genode/repos/os/src/drivers/acpi/smbios_table_reporter.cc

172 lines
5.2 KiB
C++

/*
* \brief Finding and reporting an SMBIOS table as is (plain data)
* \author Martin Stein
* \date 2019-07-09
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include <smbios_table_reporter.h>
#include <efi_system_table.h>
/* Genode includes */
#include <base/attached_io_mem_dataspace.h>
#include <base/attached_rom_dataspace.h>
#include <smbios/smbios.h>
#include <util/fifo.h>
using namespace Genode;
constexpr size_t get_page_size_log2() { return 12; }
constexpr size_t get_page_size() { return 1 << get_page_size_log2(); }
Smbios_table_reporter::Smbios_table_reporter(Env &env,
Allocator &alloc)
{
struct Io_region
{
Env &_env;
addr_t _base_page;
size_t _size_pages;
Fifo<Fifo_element<Io_region> > &_fifo;
Attached_io_mem_dataspace _io_mem { _env, _base_page, _size_pages };
Fifo_element<Io_region> _fifo_elem { *this };
Io_region(Env &env,
addr_t base_page,
size_t size_pages,
Fifo<Fifo_element<Io_region> > &fifo)
:
_env { env },
_base_page { base_page },
_size_pages { size_pages },
_fifo { fifo }
{
_fifo.enqueue(_fifo_elem);
}
~Io_region()
{
_fifo.remove(_fifo_elem);
}
};
Fifo<Fifo_element<Io_region> > io_regions;
addr_t const page_mask { ~(addr_t)((1 << get_page_size_log2()) - 1) };
addr_t const page_off_mask { get_page_size() - 1 };
auto phy_mem = [&] (addr_t base, size_t size) {
addr_t const end { base + size };
Io_region *reuse_io { nullptr };
io_regions.for_each([&] (Fifo_element<Io_region> &elem) {
Io_region &io { elem.object() };
if (io._base_page <= base &&
io._base_page + io._size_pages >= end) {
reuse_io = &io;
}
});
if (reuse_io) {
addr_t const off { base - reuse_io->_base_page };
return (addr_t)reuse_io->_io_mem.local_addr<int>() + off;
}
addr_t const base_page { base & page_mask };
addr_t const base_off { base - base_page };
size += base_off;
size_t const size_pages { (size + page_off_mask) & page_mask };
addr_t alloc_base { base_page };
addr_t alloc_end { base_page + size_pages };
io_regions.for_each([&] (Fifo_element<Io_region> &elem) {
Io_region &io { elem.object() };
addr_t const io_base { io._base_page };
addr_t const io_end { io._base_page + io._size_pages };
bool io_destroy { false };
if (io_base < alloc_base && io_end > alloc_base) {
alloc_base = io_base;
io_destroy = true;
}
if (io_base < alloc_end && io_end > alloc_end) {
alloc_end = io_end;
io_destroy = true;
}
if (io_base >= alloc_base && io_end <= alloc_end) {
io_destroy = true;
}
if (io_destroy) {
destroy(&alloc, &io);
}
});
size_t alloc_size { alloc_end - alloc_base };
Io_region *io {
new (alloc) Io_region(env, alloc_base, alloc_size, io_regions) };
addr_t const off { base - io->_base_page };
return (addr_t)io->_io_mem.local_addr<int>() + off;
};
auto report_smbios = [&] (void *ep_vir, size_t ep_size,
addr_t st_phy, size_t st_size)
{
addr_t const st_vir { phy_mem(st_phy, st_size) };
size_t const ram_size { ep_size + st_size };
addr_t const ram_vir { (addr_t)alloc.alloc(ram_size) };
memcpy((void *)ram_vir, ep_vir, ep_size);
memcpy((void *)(ram_vir + ep_size), (void *)st_vir, st_size);
_reporter.construct(env, "smbios_table", "smbios_table", ram_size);
_reporter->enabled(true);
_reporter->report((void *)ram_vir, ram_size);
alloc.free((void *)ram_vir, ep_size + st_size);
};
auto handle_smbios_3 = [&] (Smbios_3_entry_point const &ep)
{
report_smbios((void *)&ep, ep.length, ep.struct_table_addr,
ep.struct_table_max_size);
};
auto handle_smbios = [&] (Smbios_entry_point const &ep)
{
report_smbios((void *)&ep, ep.length, ep.struct_table_addr,
ep.struct_table_length);
};
auto handle_dmi = [&] (Dmi_entry_point const &ep)
{
report_smbios((void *)&ep, ep.LENGTH, ep.struct_table_addr,
ep.struct_table_length);
};
addr_t efi_sys_tab_phy = 0;
try {
Attached_rom_dataspace info(env, "platform_info");
Xml_node xml(info.local_addr<char>(), info.size());
Xml_node acpi_node = xml.sub_node("efi-system-table");
efi_sys_tab_phy = acpi_node.attribute_value("address", 0UL);
} catch (...) { }
if (!efi_sys_tab_phy) {
Smbios_table::from_scan(phy_mem, handle_smbios_3,
handle_smbios, handle_dmi);
} else {
Efi_system_table const &efi_sys_tab_vir { *(Efi_system_table *)
phy_mem(efi_sys_tab_phy, sizeof(Efi_system_table)) };
efi_sys_tab_vir.for_smbios_table(phy_mem, [&] (addr_t table_phy) {
Smbios_table::from_pointer(table_phy, phy_mem, handle_smbios_3,
handle_smbios, handle_dmi);
});
}
io_regions.for_each([&] (Fifo_element<Io_region> &elem) {
destroy(alloc, &elem.object());
});
}