1// Copyright 2017 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6//
7#include <platform/pc/smbios.h>
8
9#include <fbl/auto_call.h>
10#include <lib/console.h>
11#include <lib/smbios/smbios.h>
12#include <platform/pc/bootloader.h>
13#include <stdint.h>
14#include <string.h>
15#include <vm/physmap.h>
16#include <vm/vm_aspace.h>
17#include <vm/vm_address_region.h>
18#include <vm/vm_object_physical.h>
19#include <zircon/compiler.h>
20#include <zircon/types.h>
21
22namespace {
23
24smbios::EntryPointVersion kEpVersion = smbios::EntryPointVersion::Unknown;
25union {
26    const uint8_t* raw;
27    const smbios::EntryPoint2_1* ep2_1;
28} kEntryPoint;
29uintptr_t kStructBase = 0; // Address of first SMBIOS struct
30
31zx_status_t FindEntryPoint(const uint8_t** base, smbios::EntryPointVersion* version) {
32    // See if our bootloader told us where the table is
33    if (bootloader.smbios != 0) {
34        const uint8_t* p = reinterpret_cast<const uint8_t*>(paddr_to_physmap(bootloader.smbios));
35        if (!memcmp(p, SMBIOS2_ANCHOR, strlen(SMBIOS2_ANCHOR))) {
36            *base = p;
37            *version = smbios::EntryPointVersion::V2_1;
38            return ZX_OK;
39        } else if (!memcmp(p, SMBIOS3_ANCHOR, strlen(SMBIOS3_ANCHOR))) {
40            *base = p;
41            *version = smbios::EntryPointVersion::V3_0;
42            return ZX_OK;
43        }
44    }
45
46    // Fallback to non-EFI SMBIOS search if we haven't found it yet
47    for (paddr_t target = 0x000f0000; target < 0x00100000; target += 16) {
48        const uint8_t* p = reinterpret_cast<const uint8_t*>(paddr_to_physmap(target));
49        if (!memcmp(p, SMBIOS2_ANCHOR, strlen(SMBIOS2_ANCHOR))) {
50            *base = p;
51            *version = smbios::EntryPointVersion::V2_1;
52            return ZX_OK;
53        }
54        if (!memcmp(p, SMBIOS3_ANCHOR, strlen(SMBIOS3_ANCHOR))) {
55            *base = p;
56            *version = smbios::EntryPointVersion::V3_0;
57            return ZX_OK;
58        }
59    }
60
61    return ZX_ERR_NOT_FOUND;
62}
63
64zx_status_t MapStructs2_1(const smbios::EntryPoint2_1* ep,
65                          fbl::RefPtr<VmMapping>* mapping, uintptr_t* struct_table_virt) {
66    paddr_t base = ep->struct_table_phys;
67    paddr_t end = base + ep->struct_table_length;
68    const size_t subpage_offset = base & (PAGE_SIZE - 1);
69    base -= subpage_offset;
70    size_t len = ROUNDUP(end - base, PAGE_SIZE);
71
72    auto vmar = VmAspace::kernel_aspace()->RootVmar();
73    fbl::RefPtr<VmObject> vmo;
74    zx_status_t status = VmObjectPhysical::Create(base, len, &vmo);
75    if (status != ZX_OK) {
76        return status;
77    }
78    fbl::RefPtr<VmMapping> m;
79    status = vmar->CreateVmMapping(0, len, 0, 0 /* vmar_flags */, fbl::move(vmo), 0,
80                                   ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_READ,
81                                   "smbios", &m);
82    if (status != ZX_OK) {
83        return status;
84    }
85    *struct_table_virt = m->base() + subpage_offset;
86    *mapping = fbl::move(m);
87    return ZX_OK;
88}
89
90} // namespace
91
92// Walk the known SMBIOS structures.  The callback will be called once for each
93// structure found.
94zx_status_t SmbiosWalkStructs(smbios::StructWalkCallback cb, void* ctx) {
95    switch (kEpVersion) {
96        case smbios::EntryPointVersion::V2_1: {
97            return kEntryPoint.ep2_1->WalkStructs(kStructBase, cb, ctx);
98        }
99        case smbios::EntryPointVersion::V3_0:
100            return ZX_ERR_NOT_SUPPORTED;
101        default:
102            return ZX_ERR_NOT_SUPPORTED;
103    }
104}
105
106void pc_init_smbios() {
107    fbl::RefPtr<VmMapping> mapping;
108    auto cleanup_mapping = fbl::MakeAutoCall([&mapping] {
109        if (mapping) {
110            mapping->Destroy();
111        }
112    });
113
114    const uint8_t* start = nullptr;
115    auto version = smbios::EntryPointVersion::Unknown;
116    uintptr_t struct_table_virt = 0;
117
118    zx_status_t status = FindEntryPoint(&start, &version);
119    if (status != ZX_OK) {
120        printf("smbios: Failed to locate entry point\n");
121        return;
122    }
123
124    switch (version) {
125        case smbios::EntryPointVersion::V2_1: {
126            auto ep = reinterpret_cast<const smbios::EntryPoint2_1*>(start);
127            if (!ep->IsValid()) {
128                return;
129            }
130
131            status = MapStructs2_1(ep, &mapping, &struct_table_virt);
132            if (status != ZX_OK) {
133                printf("smbios: failed to map structs: %d\n", status);
134                return;
135            }
136            break;
137        }
138        case smbios::EntryPointVersion::V3_0:
139            printf("smbios: version 3 not yet implemented\n");
140            return;
141        default:
142            DEBUG_ASSERT(false);
143            printf("smbios: Unknown version?\n");
144            return;
145    }
146
147    kEntryPoint.raw = start;
148    kEpVersion = version;
149    kStructBase = struct_table_virt;
150    cleanup_mapping.cancel();
151}
152
153static zx_status_t DebugStructWalk(smbios::SpecVersion ver,
154                                   const smbios::Header* hdr, const smbios::StringTable& st,
155                                   void* ctx) {
156    switch (hdr->type) {
157        case smbios::StructType::BiosInfo: {
158            if (ver.IncludesVersion(2, 4)) {
159                auto entry = reinterpret_cast<const smbios::BiosInformationStruct2_4*>(hdr);
160                entry->Dump(st);
161                return ZX_OK;
162            } else if (ver.IncludesVersion(2, 0))  {
163                auto entry = reinterpret_cast<const smbios::BiosInformationStruct2_0*>(hdr);
164                entry->Dump(st);
165                return ZX_OK;
166            }
167            break;
168        }
169        case smbios::StructType::SystemInfo: {
170            if (ver.IncludesVersion(2, 4)) {
171                auto entry = reinterpret_cast<const smbios::SystemInformationStruct2_4*>(hdr);
172                entry->Dump(st);
173                return ZX_OK;
174            } else if (ver.IncludesVersion(2, 1))  {
175                auto entry = reinterpret_cast<const smbios::SystemInformationStruct2_1*>(hdr);
176                entry->Dump(st);
177                return ZX_OK;
178            } else if (ver.IncludesVersion(2, 0))  {
179                auto entry = reinterpret_cast<const smbios::SystemInformationStruct2_0*>(hdr);
180                entry->Dump(st);
181                return ZX_OK;
182            }
183            break;
184        }
185        default: break;
186    }
187    printf("smbios: found struct@%p: typ=%u len=%u st_len=%zu\n", hdr,
188           static_cast<uint8_t>(hdr->type), hdr->length, st.length());
189    st.Dump();
190
191    return ZX_OK;
192}
193
194static int CmdSmbios(int argc, const cmd_args *argv, uint32_t flags)
195{
196    if (argc < 2) {
197        printf("not enough arguments\n");
198usage:
199        printf("usage:\n");
200        printf("%s dump\n", argv[0].str);
201        return ZX_ERR_INTERNAL;
202    }
203
204    if (!strcmp(argv[1].str, "dump")) {
205        zx_status_t status = SmbiosWalkStructs(DebugStructWalk, nullptr);
206        if (status != ZX_OK) {
207            printf("smbios: failed to walk structs: %d\n", status);
208        }
209        return ZX_OK;
210    } else {
211        printf("unknown command\n");
212        goto usage;
213    }
214
215    return ZX_OK;
216}
217
218STATIC_COMMAND_START
219#if LK_DEBUGLEVEL > 0
220STATIC_COMMAND("smbios", "smbios", &CmdSmbios)
221#endif
222STATIC_COMMAND_END(smbios);
223