blob: b40b24edcb46de487634a53d4c3295f02e62aaee [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "smbios.h"
#include <ddk/driver.h>
#include <fbl/algorithm.h>
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/smbios/smbios.h>
#include <lib/zx/resource.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <limits.h>
namespace {
// Map the structure with the given physical address and length. Neither needs
// to be page-aligned.
zx_status_t MapStructure(const zx::resource& resource, zx_paddr_t paddr, size_t length,
fzl::OwnedVmoMapper* mapping, uintptr_t* offsetted_start) {
zx_paddr_t base_paddr = fbl::round_down<zx_paddr_t>(paddr, static_cast<zx_paddr_t>(PAGE_SIZE));
zx::vmo vmo;
size_t page_offset = paddr - base_paddr;
size_t mapping_size = fbl::round_up<size_t>(length + page_offset, static_cast<size_t>(PAGE_SIZE));
zx_status_t status = zx::vmo::create_physical(resource, base_paddr, mapping_size, &vmo);
if (status != ZX_OK) {
return status;
}
fzl::OwnedVmoMapper new_mapping;
status = new_mapping.Map(std::move(vmo), mapping_size, ZX_VM_PERM_READ);
if (status != ZX_OK) {
return status;
}
*mapping = std::move(new_mapping);
*offsetted_start = reinterpret_cast<uintptr_t>(mapping->start()) + page_offset;
return ZX_OK;
}
// Structure for handling the lifetime of the SMBIOS mappings
class SmbiosState {
public:
SmbiosState() = default;
~SmbiosState() = default;
// Must only be invoked once on an instance. On success, |entry_point()|
// and |struct_table_mapping()| are usable. |entry_point()| will be
// guaranteed to be a valid SMBIOS entry point structure.
zx_status_t LoadFromFirmware();
// These values are only valid as long as the instance is around.
const smbios::EntryPoint2_1* entry_point() const { return entry_point_; }
uintptr_t struct_table_start() const { return struct_table_start_; }
private:
fzl::OwnedVmoMapper entry_point_mapping_;
fzl::OwnedVmoMapper struct_table_mapping_;
const smbios::EntryPoint2_1* entry_point_ = nullptr;
uintptr_t struct_table_start_ = 0;
};
zx_status_t SmbiosState::LoadFromFirmware() {
// Please do not use get_root_resource() in new code. See ZX-1467.
zx::unowned_resource root_resource(get_root_resource());
zx_paddr_t acpi_rsdp, smbios_ep;
zx_status_t status = zx_pc_firmware_tables(root_resource->get(), &acpi_rsdp, &smbios_ep);
if (status != ZX_OK) {
return status;
}
if (smbios_ep == 0) {
return ZX_ERR_NOT_SUPPORTED;
}
// Map the entry point and see how much data we have
fzl::OwnedVmoMapper ep_mapping;
uintptr_t ep_start;
status = MapStructure(*root_resource, smbios_ep, PAGE_SIZE, &ep_mapping, &ep_start);
if (status != ZX_OK) {
return status;
}
auto ep = reinterpret_cast<const smbios::EntryPoint2_1*>(ep_start);
if (!ep->IsValid()) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
// Map the struct table
fzl::OwnedVmoMapper struct_table_mapping;
uintptr_t struct_table_start;
status = MapStructure(*root_resource, ep->struct_table_phys, ep->struct_table_length,
&struct_table_mapping, &struct_table_start);
if (status != ZX_OK) {
return status;
}
entry_point_mapping_ = std::move(ep_mapping);
struct_table_mapping_ = std::move(struct_table_mapping);
entry_point_ = ep;
struct_table_start_ = struct_table_start;
return ZX_OK;
}
} // namespace
bool smbios_product_name_is_valid(const char* product_name) {
if (product_name == nullptr || !strcmp(product_name, "<null>") || strlen(product_name) == 0) {
return false;
}
// Check if the product name is all spaces (seen on some devices)
const size_t product_name_len = strlen(product_name);
bool nonspace_found = false;
for (size_t i = 0; i < product_name_len; ++i) {
if (product_name[i] != ' ') {
nonspace_found = true;
break;
}
}
if (!nonspace_found) {
return false;
}
return true;
}
zx_status_t smbios_get_board_name(char* board_name_buffer, size_t board_name_size,
size_t* board_name_actual) {
SmbiosState smbios;
zx_status_t status = smbios.LoadFromFirmware();
if (status != ZX_OK) {
return status;
}
// Search for the product name
const char* sys_product_name = nullptr;
const char* baseboard_product_name = nullptr;
auto struct_walk_cb = [&sys_product_name, &baseboard_product_name](
smbios::SpecVersion version, const smbios::Header* h,
const smbios::StringTable& st) -> zx_status_t {
if (h->type == smbios::StructType::SystemInfo && version.IncludesVersion(2, 0)) {
auto entry = reinterpret_cast<const smbios::SystemInformationStruct2_0*>(h);
zx_status_t status = st.GetString(entry->product_name_str_idx, &sys_product_name);
if (status != ZX_OK) {
sys_product_name = nullptr;
}
} else if (h->type == smbios::StructType::Baseboard) {
auto entry = reinterpret_cast<const smbios::BaseboardInformationStruct*>(h);
zx_status_t status = st.GetString(entry->product_name_str_idx, &baseboard_product_name);
if (status != ZX_OK) {
baseboard_product_name = nullptr;
}
}
return ZX_OK;
};
status = smbios.entry_point()->WalkStructs(smbios.struct_table_start(), struct_walk_cb);
if (status != ZX_OK) {
return status;
}
const char* product_name = nullptr;
for (auto possible_name : {sys_product_name, baseboard_product_name}) {
if (smbios_product_name_is_valid(possible_name)) {
product_name = possible_name;
break;
}
}
if (product_name == nullptr) {
return ZX_ERR_NOT_FOUND;
}
size_t product_name_len = strlen(product_name) + 1;
*board_name_actual = product_name_len;
if (product_name_len > board_name_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(board_name_buffer, product_name, product_name_len);
return ZX_OK;
}