qemu/hw/acpi/bios-linker-loader.c
<<
>>
Prefs
   1/* Dynamic linker/loader of ACPI tables
   2 *
   3 * Copyright (C) 2013 Red Hat Inc
   4 *
   5 * Author: Michael S. Tsirkin <mst@redhat.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License as published by
   9 * the Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16
  17 * You should have received a copy of the GNU General Public License along
  18 * with this program; if not, see <http://www.gnu.org/licenses/>.
  19 */
  20
  21#include "qemu/osdep.h"
  22#include "hw/acpi/bios-linker-loader.h"
  23#include "hw/nvram/fw_cfg.h"
  24
  25#include "qemu/bswap.h"
  26
  27/*
  28 * Linker/loader is a paravirtualized interface that passes commands to guest.
  29 * The commands can be used to request guest to
  30 * - allocate memory chunks and initialize them from QEMU FW CFG files
  31 * - link allocated chunks by storing pointer to one chunk into another
  32 * - calculate ACPI checksum of part of the chunk and store into same chunk
  33 */
  34#define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH
  35
  36struct BiosLinkerLoaderEntry {
  37    uint32_t command;
  38    union {
  39        /*
  40         * COMMAND_ALLOCATE - allocate a table from @alloc.file
  41         * subject to @alloc.align alignment (must be power of 2)
  42         * and @alloc.zone (can be HIGH or FSEG) requirements.
  43         *
  44         * Must appear exactly once for each file, and before
  45         * this file is referenced by any other command.
  46         */
  47        struct {
  48            char file[BIOS_LINKER_LOADER_FILESZ];
  49            uint32_t align;
  50            uint8_t zone;
  51        } alloc;
  52
  53        /*
  54         * COMMAND_ADD_POINTER - patch the table (originating from
  55         * @dest_file) at @pointer.offset, by adding a pointer to the table
  56         * originating from @src_file. 1,2,4 or 8 byte unsigned
  57         * addition is used depending on @pointer.size.
  58         */
  59        struct {
  60            char dest_file[BIOS_LINKER_LOADER_FILESZ];
  61            char src_file[BIOS_LINKER_LOADER_FILESZ];
  62            uint32_t offset;
  63            uint8_t size;
  64        } pointer;
  65
  66        /*
  67         * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by
  68         * @cksum_start and @cksum_length fields,
  69         * and then add the value at @cksum.offset.
  70         * Checksum simply sums -X for each byte X in the range
  71         * using 8-bit math.
  72         */
  73        struct {
  74            char file[BIOS_LINKER_LOADER_FILESZ];
  75            uint32_t offset;
  76            uint32_t start;
  77            uint32_t length;
  78        } cksum;
  79
  80        /*
  81         * COMMAND_WRITE_POINTER - write the fw_cfg file (originating from
  82         * @dest_file) at @wr_pointer.offset, by adding a pointer to
  83         * @src_offset within the table originating from @src_file.
  84         * 1,2,4 or 8 byte unsigned addition is used depending on
  85         * @wr_pointer.size.
  86         */
  87        struct {
  88            char dest_file[BIOS_LINKER_LOADER_FILESZ];
  89            char src_file[BIOS_LINKER_LOADER_FILESZ];
  90            uint32_t dst_offset;
  91            uint32_t src_offset;
  92            uint8_t size;
  93        } wr_pointer;
  94
  95        /* padding */
  96        char pad[124];
  97    };
  98} QEMU_PACKED;
  99typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry;
 100
 101enum {
 102    BIOS_LINKER_LOADER_COMMAND_ALLOCATE          = 0x1,
 103    BIOS_LINKER_LOADER_COMMAND_ADD_POINTER       = 0x2,
 104    BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM      = 0x3,
 105    BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER     = 0x4,
 106};
 107
 108enum {
 109    BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1,
 110    BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2,
 111};
 112
 113/*
 114 * BiosLinkerFileEntry:
 115 *
 116 * An internal type used for book-keeping file entries
 117 */
 118typedef struct BiosLinkerFileEntry {
 119    char *name; /* file name */
 120    GArray *blob; /* data accosiated with @name */
 121} BiosLinkerFileEntry;
 122
 123/*
 124 * bios_linker_loader_init: allocate a new linker object instance.
 125 *
 126 * After initialization, linker commands can be added, and will
 127 * be stored in the linker.cmd_blob array.
 128 */
 129BIOSLinker *bios_linker_loader_init(void)
 130{
 131    BIOSLinker *linker = g_new(BIOSLinker, 1);
 132
 133    linker->cmd_blob = g_array_new(false, true /* clear */, 1);
 134    linker->file_list = g_array_new(false, true /* clear */,
 135                                    sizeof(BiosLinkerFileEntry));
 136    return linker;
 137}
 138
 139/* Free linker wrapper */
 140void bios_linker_loader_cleanup(BIOSLinker *linker)
 141{
 142    int i;
 143    BiosLinkerFileEntry *entry;
 144
 145    g_array_free(linker->cmd_blob, true);
 146
 147    for (i = 0; i < linker->file_list->len; i++) {
 148        entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i);
 149        g_free(entry->name);
 150    }
 151    g_array_free(linker->file_list, true);
 152    g_free(linker);
 153}
 154
 155static const BiosLinkerFileEntry *
 156bios_linker_find_file(const BIOSLinker *linker, const char *name)
 157{
 158    int i;
 159    BiosLinkerFileEntry *entry;
 160
 161    for (i = 0; i < linker->file_list->len; i++) {
 162        entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i);
 163        if (!strcmp(entry->name, name)) {
 164            return entry;
 165        }
 166    }
 167    return NULL;
 168}
 169
 170/*
 171 * board code must realize fw_cfg first, as a fixed device, before
 172 * another device realize function call bios_linker_loader_can_write_pointer()
 173 */
 174bool bios_linker_loader_can_write_pointer(void)
 175{
 176    FWCfgState *fw_cfg = fw_cfg_find();
 177    return fw_cfg && fw_cfg_dma_enabled(fw_cfg);
 178}
 179
 180/*
 181 * bios_linker_loader_alloc: ask guest to load file into guest memory.
 182 *
 183 * @linker: linker object instance
 184 * @file_name: name of the file blob to be loaded
 185 * @file_blob: pointer to blob corresponding to @file_name
 186 * @alloc_align: required minimal alignment in bytes. Must be a power of 2.
 187 * @alloc_fseg: request allocation in FSEG zone (useful for the RSDP ACPI table)
 188 *
 189 * Note: this command must precede any other linker command using this file.
 190 */
 191void bios_linker_loader_alloc(BIOSLinker *linker,
 192                              const char *file_name,
 193                              GArray *file_blob,
 194                              uint32_t alloc_align,
 195                              bool alloc_fseg)
 196{
 197    BiosLinkerLoaderEntry entry;
 198    BiosLinkerFileEntry file = { g_strdup(file_name), file_blob};
 199
 200    assert(!(alloc_align & (alloc_align - 1)));
 201
 202    assert(!bios_linker_find_file(linker, file_name));
 203    g_array_append_val(linker->file_list, file);
 204
 205    memset(&entry, 0, sizeof entry);
 206    strncpy(entry.alloc.file, file_name, sizeof entry.alloc.file - 1);
 207    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ALLOCATE);
 208    entry.alloc.align = cpu_to_le32(alloc_align);
 209    entry.alloc.zone = alloc_fseg ? BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG :
 210                                    BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH;
 211
 212    /* Alloc entries must come first, so prepend them */
 213    g_array_prepend_vals(linker->cmd_blob, &entry, sizeof entry);
 214}
 215
 216/*
 217 * bios_linker_loader_add_checksum: ask guest to add checksum of ACPI
 218 * table in the specified file at the specified offset.
 219 *
 220 * Checksum calculation simply sums -X for each byte X in the range
 221 * using 8-bit math (i.e. ACPI checksum).
 222 *
 223 * @linker: linker object instance
 224 * @file: file that includes the checksum to be calculated
 225 *        and the data to be checksummed
 226 * @start_offset, @size: range of data in the file to checksum,
 227 *                       relative to the start of file blob
 228 * @checksum_offset: location of the checksum to be patched within file blob,
 229 *                   relative to the start of file blob
 230 */
 231void bios_linker_loader_add_checksum(BIOSLinker *linker, const char *file_name,
 232                                     unsigned start_offset, unsigned size,
 233                                     unsigned checksum_offset)
 234{
 235    BiosLinkerLoaderEntry entry;
 236    const BiosLinkerFileEntry *file = bios_linker_find_file(linker, file_name);
 237
 238    assert(file);
 239    assert(start_offset < file->blob->len);
 240    assert(start_offset + size <= file->blob->len);
 241    assert(checksum_offset >= start_offset);
 242    assert(checksum_offset + 1 <= start_offset + size);
 243
 244    *(file->blob->data + checksum_offset) = 0;
 245    memset(&entry, 0, sizeof entry);
 246    strncpy(entry.cksum.file, file_name, sizeof entry.cksum.file - 1);
 247    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM);
 248    entry.cksum.offset = cpu_to_le32(checksum_offset);
 249    entry.cksum.start = cpu_to_le32(start_offset);
 250    entry.cksum.length = cpu_to_le32(size);
 251
 252    g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
 253}
 254
 255/*
 256 * bios_linker_loader_add_pointer: ask guest to patch address in
 257 * destination file with a pointer to source file
 258 *
 259 * @linker: linker object instance
 260 * @dest_file: destination file that must be changed
 261 * @dst_patched_offset: location within destination file blob to be patched
 262 *                      with the pointer to @src_file+@src_offset (i.e. source
 263 *                      blob allocated in guest memory + @src_offset), in bytes
 264 * @dst_patched_offset_size: size of the pointer to be patched
 265 *                      at @dst_patched_offset in @dest_file blob, in bytes
 266 * @src_file: source file who's address must be taken
 267 * @src_offset: location within source file blob to which
 268 *              @dest_file+@dst_patched_offset will point to after
 269 *              firmware's executed ADD_POINTER command
 270 */
 271void bios_linker_loader_add_pointer(BIOSLinker *linker,
 272                                    const char *dest_file,
 273                                    uint32_t dst_patched_offset,
 274                                    uint8_t dst_patched_size,
 275                                    const char *src_file,
 276                                    uint32_t src_offset)
 277{
 278    uint64_t le_src_offset;
 279    BiosLinkerLoaderEntry entry;
 280    const BiosLinkerFileEntry *dst_file =
 281        bios_linker_find_file(linker, dest_file);
 282    const BiosLinkerFileEntry *source_file =
 283        bios_linker_find_file(linker, src_file);
 284
 285    assert(dst_file);
 286    assert(source_file);
 287    assert(dst_patched_offset < dst_file->blob->len);
 288    assert(dst_patched_offset + dst_patched_size <= dst_file->blob->len);
 289    assert(src_offset < source_file->blob->len);
 290
 291    memset(&entry, 0, sizeof entry);
 292    strncpy(entry.pointer.dest_file, dest_file,
 293            sizeof entry.pointer.dest_file - 1);
 294    strncpy(entry.pointer.src_file, src_file,
 295            sizeof entry.pointer.src_file - 1);
 296    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_POINTER);
 297    entry.pointer.offset = cpu_to_le32(dst_patched_offset);
 298    entry.pointer.size = dst_patched_size;
 299    assert(dst_patched_size == 1 || dst_patched_size == 2 ||
 300           dst_patched_size == 4 || dst_patched_size == 8);
 301
 302    le_src_offset = cpu_to_le64(src_offset);
 303    memcpy(dst_file->blob->data + dst_patched_offset,
 304           &le_src_offset, dst_patched_size);
 305
 306    g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
 307}
 308
 309/*
 310 * bios_linker_loader_write_pointer: ask guest to write a pointer to the
 311 * source file into the destination file, and write it back to QEMU via
 312 * fw_cfg DMA.
 313 *
 314 * @linker: linker object instance
 315 * @dest_file: destination file that must be written
 316 * @dst_patched_offset: location within destination file blob to be patched
 317 *                      with the pointer to @src_file, in bytes
 318 * @dst_patched_offset_size: size of the pointer to be patched
 319 *                      at @dst_patched_offset in @dest_file blob, in bytes
 320 * @src_file: source file who's address must be taken
 321 * @src_offset: location within source file blob to which
 322 *              @dest_file+@dst_patched_offset will point to after
 323 *              firmware's executed WRITE_POINTER command
 324 */
 325void bios_linker_loader_write_pointer(BIOSLinker *linker,
 326                                    const char *dest_file,
 327                                    uint32_t dst_patched_offset,
 328                                    uint8_t dst_patched_size,
 329                                    const char *src_file,
 330                                    uint32_t src_offset)
 331{
 332    BiosLinkerLoaderEntry entry;
 333    const BiosLinkerFileEntry *source_file =
 334        bios_linker_find_file(linker, src_file);
 335
 336    assert(source_file);
 337    assert(src_offset < source_file->blob->len);
 338    memset(&entry, 0, sizeof entry);
 339    strncpy(entry.wr_pointer.dest_file, dest_file,
 340            sizeof entry.wr_pointer.dest_file - 1);
 341    strncpy(entry.wr_pointer.src_file, src_file,
 342            sizeof entry.wr_pointer.src_file - 1);
 343    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER);
 344    entry.wr_pointer.dst_offset = cpu_to_le32(dst_patched_offset);
 345    entry.wr_pointer.src_offset = cpu_to_le32(src_offset);
 346    entry.wr_pointer.size = dst_patched_size;
 347    assert(dst_patched_size == 1 || dst_patched_size == 2 ||
 348           dst_patched_size == 4 || dst_patched_size == 8);
 349
 350    g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
 351}
 352