mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 14:57:35 +00:00
Kernel: Add convenient ways to map whole BIOS and EBDA into memory
This patch adds a MappedROM abstraction to the Kernel VM subsystem. It's basically the read-only byte buffer equivalent of a TypedMapping. We use this in the ACPI and MP table parsers to scan for interesting stuff in low memory instead of doing a bunch of address arithmetic.
This commit is contained in:
parent
6b5d2afd00
commit
84b7bc5e14
8 changed files with 169 additions and 47 deletions
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
#include <Kernel/ACPI/MultiProcessorParser.h>
|
#include <Kernel/ACPI/MultiProcessorParser.h>
|
||||||
|
#include <Kernel/Arch/PC/BIOS.h>
|
||||||
#include <Kernel/Interrupts/IOAPIC.h>
|
#include <Kernel/Interrupts/IOAPIC.h>
|
||||||
#include <Kernel/StdLib.h>
|
#include <Kernel/StdLib.h>
|
||||||
#include <Kernel/VM/MemoryManager.h>
|
#include <Kernel/VM/MemoryManager.h>
|
||||||
|
@ -115,42 +116,28 @@ void MultiProcessorParser::parse_configuration_table()
|
||||||
|
|
||||||
PhysicalAddress MultiProcessorParser::search_floating_pointer()
|
PhysicalAddress MultiProcessorParser::search_floating_pointer()
|
||||||
{
|
{
|
||||||
PhysicalAddress mp_floating_pointer;
|
auto mp_floating_pointer = search_floating_pointer_in_ebda();
|
||||||
auto region = MM.allocate_kernel_region(PhysicalAddress(0), PAGE_SIZE, "MultiProcessor Parser Floating Pointer Structure Finding", Region::Access::Read);
|
|
||||||
u16 ebda_seg = (u16) * ((uint16_t*)((region->vaddr().get() & PAGE_MASK) + 0x40e));
|
|
||||||
klog() << "MultiProcessor: Probing EBDA, Segment 0x" << String::format("%x", ebda_seg);
|
|
||||||
|
|
||||||
mp_floating_pointer = search_floating_pointer_in_ebda(ebda_seg);
|
|
||||||
if (!mp_floating_pointer.is_null())
|
if (!mp_floating_pointer.is_null())
|
||||||
return mp_floating_pointer;
|
return mp_floating_pointer;
|
||||||
return search_floating_pointer_in_bios_area();
|
return search_floating_pointer_in_bios_area();
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalAddress MultiProcessorParser::search_floating_pointer_in_ebda(u16 ebda_segment)
|
PhysicalAddress MultiProcessorParser::search_floating_pointer_in_ebda()
|
||||||
{
|
{
|
||||||
auto floating_pointer_region = MM.allocate_kernel_region(PhysicalAddress(page_base_of((u32)(ebda_segment << 4))), PAGE_ROUND_UP(1024), "MultiProcessor Parser floating_pointer Finding #1", Region::Access::Read, false, true);
|
klog() << "MultiProcessor: Probing EBDA";
|
||||||
char* p_floating_pointer_str = (char*)(PhysicalAddress(ebda_segment << 4).as_ptr());
|
auto ebda = map_ebda();
|
||||||
for (char* floating_pointer_str = (char*)floating_pointer_region->vaddr().offset(offset_in_page((u32)(ebda_segment << 4))).as_ptr(); floating_pointer_str < (char*)(floating_pointer_region->vaddr().offset(offset_in_page((u32)(ebda_segment << 4))).get() + 1024); floating_pointer_str += 16) {
|
for (auto* floating_pointer_str = ebda.base(); floating_pointer_str < ebda.end(); floating_pointer_str += 16) {
|
||||||
#ifdef MULTIPROCESSOR_DEBUG
|
if (!strncmp("_MP_", (const char*)floating_pointer_str, strlen("_MP_")))
|
||||||
//dbg() << "MultiProcessor: Looking for floating pointer structure in EBDA @ V0x " << String::format("%x", floating_pointer_str) << ", P0x" << String::format("%x", p_floating_pointer_str);
|
return ebda.paddr_of(floating_pointer_str);
|
||||||
#endif
|
|
||||||
if (!strncmp("_MP_", floating_pointer_str, strlen("_MP_")))
|
|
||||||
return PhysicalAddress((FlatPtr)p_floating_pointer_str);
|
|
||||||
p_floating_pointer_str += 16;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
PhysicalAddress MultiProcessorParser::search_floating_pointer_in_bios_area()
|
PhysicalAddress MultiProcessorParser::search_floating_pointer_in_bios_area()
|
||||||
{
|
{
|
||||||
auto floating_pointer_region = MM.allocate_kernel_region(PhysicalAddress(page_base_of((u32)0xE0000)), PAGE_ROUND_UP(0xFFFFF - 0xE0000), "MultiProcessor Parser floating_pointer Finding #2", Region::Access::Read, false, true);
|
auto bios = map_bios();
|
||||||
char* p_floating_pointer_str = (char*)(PhysicalAddress(0xE0000).as_ptr());
|
for (auto* floating_pointer_str = bios.base(); floating_pointer_str < bios.end(); floating_pointer_str += 16) {
|
||||||
for (char* floating_pointer_str = (char*)floating_pointer_region->vaddr().offset(offset_in_page((u32)(0xE0000))).as_ptr(); floating_pointer_str < (char*)(floating_pointer_region->vaddr().offset(offset_in_page((u32)(0xE0000))).get() + (0xFFFFF - 0xE0000)); floating_pointer_str += 16) {
|
if (!strncmp("_MP_", (const char*)floating_pointer_str, strlen("_MP_")))
|
||||||
#ifdef MULTIPROCESSOR_DEBUG
|
return bios.paddr_of(floating_pointer_str);
|
||||||
//dbg() << "MultiProcessor: Looking for floating pointer structure in BIOS area @ V0x " << String::format("%x", floating_pointer_str) << ", P0x" << String::format("%x", p_floating_pointer_str);
|
|
||||||
#endif
|
|
||||||
if (!strncmp("_MP_", floating_pointer_str, strlen("_MP_")))
|
|
||||||
return PhysicalAddress((FlatPtr)p_floating_pointer_str);
|
|
||||||
p_floating_pointer_str += 16;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,7 +206,7 @@ protected:
|
||||||
Vector<u8> get_pci_bus_ids() const;
|
Vector<u8> get_pci_bus_ids() const;
|
||||||
|
|
||||||
PhysicalAddress search_floating_pointer();
|
PhysicalAddress search_floating_pointer();
|
||||||
PhysicalAddress search_floating_pointer_in_ebda(u16 ebda_segment);
|
PhysicalAddress search_floating_pointer_in_ebda();
|
||||||
PhysicalAddress search_floating_pointer_in_bios_area();
|
PhysicalAddress search_floating_pointer_in_bios_area();
|
||||||
|
|
||||||
PhysicalAddress m_floating_pointer;
|
PhysicalAddress m_floating_pointer;
|
||||||
|
|
|
@ -27,11 +27,12 @@
|
||||||
|
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
#include <Kernel/ACPI/Parser.h>
|
#include <Kernel/ACPI/Parser.h>
|
||||||
|
#include <Kernel/Arch/PC/BIOS.h>
|
||||||
|
#include <Kernel/IO.h>
|
||||||
#include <Kernel/PCI/Access.h>
|
#include <Kernel/PCI/Access.h>
|
||||||
|
#include <Kernel/StdLib.h>
|
||||||
#include <Kernel/VM/MemoryManager.h>
|
#include <Kernel/VM/MemoryManager.h>
|
||||||
#include <Kernel/VM/TypedMapping.h>
|
#include <Kernel/VM/TypedMapping.h>
|
||||||
#include <Kernel/IO.h>
|
|
||||||
#include <Kernel/StdLib.h>
|
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
namespace ACPI {
|
namespace ACPI {
|
||||||
|
@ -321,32 +322,25 @@ Parser::Parser(PhysicalAddress rsdp)
|
||||||
locate_static_data();
|
locate_static_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
static PhysicalAddress find_rsdp_in_ebda(u16 ebda_segment)
|
static PhysicalAddress find_rsdp_in_ebda()
|
||||||
{
|
{
|
||||||
auto rsdp_region = MM.allocate_kernel_region(PhysicalAddress(page_base_of((u32)(ebda_segment << 4))), PAGE_ROUND_UP(1024), "ACPI Static Parser RSDP Finding #1", Region::Access::Read, false, true);
|
auto ebda = map_ebda();
|
||||||
char* p_rsdp_str = (char*)(PhysicalAddress(ebda_segment << 4).as_ptr());
|
for (auto* rsdp_str = ebda.base(); rsdp_str < ebda.end(); rsdp_str += 16) {
|
||||||
for (char* rsdp_str = (char*)rsdp_region->vaddr().offset(offset_in_page((u32)(ebda_segment << 4))).as_ptr(); rsdp_str < (char*)(rsdp_region->vaddr().offset(offset_in_page((u32)(ebda_segment << 4))).get() + 1024); rsdp_str += 16) {
|
|
||||||
#ifdef ACPI_DEBUG
|
#ifdef ACPI_DEBUG
|
||||||
dbg() << "ACPI: Looking for RSDP in EBDA @ V " << (void*)rsdp_str << ", P " << (void*)p_rsdp_str;
|
dbg() << "ACPI: Looking for RSDP in EBDA @ V " << (void*)rsdp_str << ", P " << (void*)p_rsdp_str;
|
||||||
#endif
|
#endif
|
||||||
if (!strncmp("RSD PTR ", rsdp_str, strlen("RSD PTR ")))
|
if (!strncmp("RSD PTR ", (const char*)rsdp_str, strlen("RSD PTR ")))
|
||||||
return PhysicalAddress((FlatPtr)p_rsdp_str);
|
return ebda.paddr_of(rsdp_str);
|
||||||
p_rsdp_str += 16;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
static PhysicalAddress find_rsdp_in_bios_area()
|
static PhysicalAddress find_rsdp_in_bios_area()
|
||||||
{
|
{
|
||||||
auto rsdp_region = MM.allocate_kernel_region(PhysicalAddress(0xE0000), PAGE_ROUND_UP(0xFFFFF - 0xE0000), "ACPI Static Parser RSDP Finding #2", Region::Access::Read, false, true);
|
auto bios = map_bios();
|
||||||
char* p_rsdp_str = (char*)(PhysicalAddress(0xE0000).as_ptr());
|
for (auto* rsdp_str = bios.base(); rsdp_str < bios.end(); rsdp_str += 16) {
|
||||||
for (char* rsdp_str = (char*)rsdp_region->vaddr().offset(offset_in_page((u32)(0xE0000))).as_ptr(); rsdp_str < (char*)(rsdp_region->vaddr().offset(offset_in_page((u32)(0xE0000))).get() + (0xFFFFF - 0xE0000)); rsdp_str += 16) {
|
if (!strncmp("RSD PTR ", (const char*)rsdp_str, strlen("RSD PTR ")))
|
||||||
#ifdef ACPI_DEBUG
|
return bios.paddr_of(rsdp_str);
|
||||||
dbg() << "ACPI: Looking for RSDP in BIOS ROM area @ V " << (void*)rsdp_str << ", P " << (void*)p_rsdp_str;
|
|
||||||
#endif
|
|
||||||
if (!strncmp("RSD PTR ", rsdp_str, strlen("RSD PTR ")))
|
|
||||||
return PhysicalAddress((FlatPtr)p_rsdp_str);
|
|
||||||
p_rsdp_str += 16;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -364,9 +358,8 @@ static bool validate_table(const Structures::SDTHeader& v_header, size_t length)
|
||||||
|
|
||||||
PhysicalAddress StaticParsing::find_rsdp()
|
PhysicalAddress StaticParsing::find_rsdp()
|
||||||
{
|
{
|
||||||
auto ebda_seg_ptr = map_typed<u16>(PhysicalAddress(0x40e));
|
klog() << "ACPI: Probing EBDA";
|
||||||
klog() << "ACPI: Probing EBDA, Segment 0x" << String::format("%x", *ebda_seg_ptr);
|
auto rsdp = find_rsdp_in_ebda();
|
||||||
auto rsdp = find_rsdp_in_ebda(*ebda_seg_ptr);
|
|
||||||
if (!rsdp.is_null())
|
if (!rsdp.is_null())
|
||||||
return rsdp;
|
return rsdp;
|
||||||
return find_rsdp_in_bios_area();
|
return find_rsdp_in_bios_area();
|
||||||
|
|
59
Kernel/Arch/PC/BIOS.cpp
Normal file
59
Kernel/Arch/PC/BIOS.cpp
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/StringView.h>
|
||||||
|
#include <Kernel/Arch/PC/BIOS.h>
|
||||||
|
#include <Kernel/VM/MemoryManager.h>
|
||||||
|
#include <Kernel/VM/TypedMapping.h>
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
MappedROM map_bios()
|
||||||
|
{
|
||||||
|
MappedROM mapping;
|
||||||
|
mapping.size = 128 * KB;
|
||||||
|
mapping.paddr = PhysicalAddress(0xe0000);
|
||||||
|
mapping.region = MM.allocate_kernel_region(mapping.paddr, PAGE_ROUND_UP(mapping.size), {}, Region::Access::Read);
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappedROM map_ebda()
|
||||||
|
{
|
||||||
|
auto ebda_segment_ptr = map_typed<u16>(PhysicalAddress(0x40e));
|
||||||
|
auto ebda_length_ptr = map_typed<u16>(PhysicalAddress(0x413));
|
||||||
|
|
||||||
|
PhysicalAddress ebda_paddr(*ebda_segment_ptr << 4);
|
||||||
|
size_t ebda_size = *ebda_length_ptr;
|
||||||
|
|
||||||
|
MappedROM mapping;
|
||||||
|
mapping.region = MM.allocate_kernel_region(ebda_paddr.page_base(), PAGE_ROUND_UP(ebda_size), {}, Region::Access::Read);
|
||||||
|
mapping.offset = ebda_paddr.offset_in_page();
|
||||||
|
mapping.size = ebda_size;
|
||||||
|
mapping.paddr = ebda_paddr;
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
Kernel/Arch/PC/BIOS.h
Normal file
36
Kernel/Arch/PC/BIOS.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Kernel/VM/MappedROM.h>
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
MappedROM map_bios();
|
||||||
|
MappedROM map_ebda();
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ set(KERNEL_SOURCES
|
||||||
ACPI/MultiProcessorParser.cpp
|
ACPI/MultiProcessorParser.cpp
|
||||||
ACPI/Parser.cpp
|
ACPI/Parser.cpp
|
||||||
Arch/i386/CPU.cpp
|
Arch/i386/CPU.cpp
|
||||||
|
Arch/PC/BIOS.cpp
|
||||||
CMOS.cpp
|
CMOS.cpp
|
||||||
CommandLine.cpp
|
CommandLine.cpp
|
||||||
Console.cpp
|
Console.cpp
|
||||||
|
|
|
@ -44,6 +44,7 @@ class InodeWatcher;
|
||||||
class KBuffer;
|
class KBuffer;
|
||||||
class KResult;
|
class KResult;
|
||||||
class LocalSocket;
|
class LocalSocket;
|
||||||
|
class MappedROM;
|
||||||
class PageDirectory;
|
class PageDirectory;
|
||||||
class PerformanceEventBuffer;
|
class PerformanceEventBuffer;
|
||||||
class PhysicalPage;
|
class PhysicalPage;
|
||||||
|
|
45
Kernel/VM/MappedROM.h
Normal file
45
Kernel/VM/MappedROM.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/OwnPtr.h>
|
||||||
|
#include <Kernel/VM/Region.h>
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
struct MappedROM {
|
||||||
|
const u8* base() const { return region->vaddr().offset(offset).as_ptr(); }
|
||||||
|
const u8* end() const { return base() + size; }
|
||||||
|
OwnPtr<Region> region;
|
||||||
|
size_t size { 0 };
|
||||||
|
size_t offset { 0 };
|
||||||
|
PhysicalAddress paddr;
|
||||||
|
|
||||||
|
PhysicalAddress paddr_of(const u8* ptr) const { return paddr.offset(ptr - this->base()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue