bios-linker-loader.c 12.5 KB
Newer Older
M
Michael S. Tsirkin 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* Dynamic linker/loader of ACPI tables
 *
 * Copyright (C) 2013 Red Hat Inc
 *
 * Author: Michael S. Tsirkin <mst@redhat.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <http://www.gnu.org/licenses/>.
 */

P
Peter Maydell 已提交
21
#include "qemu/osdep.h"
22
#include "qemu-common.h"
23
#include "hw/acpi/bios-linker-loader.h"
M
Michael S. Tsirkin 已提交
24 25 26 27
#include "hw/nvram/fw_cfg.h"

#include "qemu/bswap.h"

28 29 30 31 32 33 34
/*
 * Linker/loader is a paravirtualized interface that passes commands to guest.
 * The commands can be used to request guest to
 * - allocate memory chunks and initialize them from QEMU FW CFG files
 * - link allocated chunks by storing pointer to one chunk into another
 * - calculate ACPI checksum of part of the chunk and store into same chunk
 */
M
Michael S. Tsirkin 已提交
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
#define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH

struct BiosLinkerLoaderEntry {
    uint32_t command;
    union {
        /*
         * COMMAND_ALLOCATE - allocate a table from @alloc.file
         * subject to @alloc.align alignment (must be power of 2)
         * and @alloc.zone (can be HIGH or FSEG) requirements.
         *
         * Must appear exactly once for each file, and before
         * this file is referenced by any other command.
         */
        struct {
            char file[BIOS_LINKER_LOADER_FILESZ];
            uint32_t align;
            uint8_t zone;
        } alloc;

        /*
         * COMMAND_ADD_POINTER - patch the table (originating from
         * @dest_file) at @pointer.offset, by adding a pointer to the table
         * originating from @src_file. 1,2,4 or 8 byte unsigned
         * addition is used depending on @pointer.size.
         */
        struct {
            char dest_file[BIOS_LINKER_LOADER_FILESZ];
            char src_file[BIOS_LINKER_LOADER_FILESZ];
            uint32_t offset;
            uint8_t size;
        } pointer;

        /*
         * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by
         * @cksum_start and @cksum_length fields,
         * and then add the value at @cksum.offset.
         * Checksum simply sums -X for each byte X in the range
         * using 8-bit math.
         */
        struct {
            char file[BIOS_LINKER_LOADER_FILESZ];
            uint32_t offset;
            uint32_t start;
            uint32_t length;
        } cksum;

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
        /*
         * COMMAND_WRITE_POINTER - write the fw_cfg file (originating from
         * @dest_file) at @wr_pointer.offset, by adding a pointer to
         * @src_offset within the table originating from @src_file.
         * 1,2,4 or 8 byte unsigned addition is used depending on
         * @wr_pointer.size.
         */
        struct {
            char dest_file[BIOS_LINKER_LOADER_FILESZ];
            char src_file[BIOS_LINKER_LOADER_FILESZ];
            uint32_t dst_offset;
            uint32_t src_offset;
            uint8_t size;
        } wr_pointer;

M
Michael S. Tsirkin 已提交
96 97 98 99 100 101 102
        /* padding */
        char pad[124];
    };
} QEMU_PACKED;
typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry;

enum {
103 104 105 106
    BIOS_LINKER_LOADER_COMMAND_ALLOCATE          = 0x1,
    BIOS_LINKER_LOADER_COMMAND_ADD_POINTER       = 0x2,
    BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM      = 0x3,
    BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER     = 0x4,
M
Michael S. Tsirkin 已提交
107 108 109 110 111 112 113
};

enum {
    BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1,
    BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2,
};

114 115 116 117 118 119 120 121 122 123
/*
 * BiosLinkerFileEntry:
 *
 * An internal type used for book-keeping file entries
 */
typedef struct BiosLinkerFileEntry {
    char *name; /* file name */
    GArray *blob; /* data accosiated with @name */
} BiosLinkerFileEntry;

124
/*
125
 * bios_linker_loader_init: allocate a new linker object instance.
126 127
 *
 * After initialization, linker commands can be added, and will
128
 * be stored in the linker.cmd_blob array.
129
 */
130
BIOSLinker *bios_linker_loader_init(void)
M
Michael S. Tsirkin 已提交
131
{
132 133 134
    BIOSLinker *linker = g_new(BIOSLinker, 1);

    linker->cmd_blob = g_array_new(false, true /* clear */, 1);
135 136
    linker->file_list = g_array_new(false, true /* clear */,
                                    sizeof(BiosLinkerFileEntry));
137
    return linker;
M
Michael S. Tsirkin 已提交
138 139
}

140 141
/* Free linker wrapper */
void bios_linker_loader_cleanup(BIOSLinker *linker)
M
Michael S. Tsirkin 已提交
142
{
143 144
    int i;
    BiosLinkerFileEntry *entry;
145 146

    g_array_free(linker->cmd_blob, true);
147

148 149 150 151 152
    for (i = 0; i < linker->file_list->len; i++) {
        entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i);
        g_free(entry->name);
    }
    g_array_free(linker->file_list, true);
153
    g_free(linker);
M
Michael S. Tsirkin 已提交
154 155
}

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
static const BiosLinkerFileEntry *
bios_linker_find_file(const BIOSLinker *linker, const char *name)
{
    int i;
    BiosLinkerFileEntry *entry;

    for (i = 0; i < linker->file_list->len; i++) {
        entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i);
        if (!strcmp(entry->name, name)) {
            return entry;
        }
    }
    return NULL;
}

171 172 173
/*
 * bios_linker_loader_alloc: ask guest to load file into guest memory.
 *
174
 * @linker: linker object instance
175 176
 * @file_name: name of the file blob to be loaded
 * @file_blob: pointer to blob corresponding to @file_name
177 178 179 180 181
 * @alloc_align: required minimal alignment in bytes. Must be a power of 2.
 * @alloc_fseg: request allocation in FSEG zone (useful for the RSDP ACPI table)
 *
 * Note: this command must precede any other linker command using this file.
 */
182
void bios_linker_loader_alloc(BIOSLinker *linker,
183 184
                              const char *file_name,
                              GArray *file_blob,
M
Michael S. Tsirkin 已提交
185 186 187 188
                              uint32_t alloc_align,
                              bool alloc_fseg)
{
    BiosLinkerLoaderEntry entry;
189
    BiosLinkerFileEntry file = { g_strdup(file_name), file_blob};
M
Michael S. Tsirkin 已提交
190

191 192
    assert(!(alloc_align & (alloc_align - 1)));

193 194 195
    assert(!bios_linker_find_file(linker, file_name));
    g_array_append_val(linker->file_list, file);

M
Michael S. Tsirkin 已提交
196
    memset(&entry, 0, sizeof entry);
197
    strncpy(entry.alloc.file, file_name, sizeof entry.alloc.file - 1);
M
Michael S. Tsirkin 已提交
198 199
    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ALLOCATE);
    entry.alloc.align = cpu_to_le32(alloc_align);
200 201
    entry.alloc.zone = alloc_fseg ? BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG :
                                    BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH;
M
Michael S. Tsirkin 已提交
202 203

    /* Alloc entries must come first, so prepend them */
204
    g_array_prepend_vals(linker->cmd_blob, &entry, sizeof entry);
M
Michael S. Tsirkin 已提交
205 206
}

207
/*
208 209
 * bios_linker_loader_add_checksum: ask guest to add checksum of ACPI
 * table in the specified file at the specified offset.
210 211 212 213
 *
 * Checksum calculation simply sums -X for each byte X in the range
 * using 8-bit math (i.e. ACPI checksum).
 *
214
 * @linker: linker object instance
215 216
 * @file: file that includes the checksum to be calculated
 *        and the data to be checksummed
217 218 219 220
 * @start_offset, @size: range of data in the file to checksum,
 *                       relative to the start of file blob
 * @checksum_offset: location of the checksum to be patched within file blob,
 *                   relative to the start of file blob
221
 */
222
void bios_linker_loader_add_checksum(BIOSLinker *linker, const char *file_name,
223 224
                                     unsigned start_offset, unsigned size,
                                     unsigned checksum_offset)
M
Michael S. Tsirkin 已提交
225 226
{
    BiosLinkerLoaderEntry entry;
227
    const BiosLinkerFileEntry *file = bios_linker_find_file(linker, file_name);
228

229 230
    assert(file);
    assert(start_offset < file->blob->len);
231
    assert(start_offset + size <= file->blob->len);
232 233
    assert(checksum_offset >= start_offset);
    assert(checksum_offset + 1 <= start_offset + size);
M
Michael S. Tsirkin 已提交
234

235
    *(file->blob->data + checksum_offset) = 0;
M
Michael S. Tsirkin 已提交
236
    memset(&entry, 0, sizeof entry);
237
    strncpy(entry.cksum.file, file_name, sizeof entry.cksum.file - 1);
M
Michael S. Tsirkin 已提交
238
    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM);
239 240
    entry.cksum.offset = cpu_to_le32(checksum_offset);
    entry.cksum.start = cpu_to_le32(start_offset);
M
Michael S. Tsirkin 已提交
241 242
    entry.cksum.length = cpu_to_le32(size);

243
    g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
M
Michael S. Tsirkin 已提交
244 245
}

246
/*
247 248
 * bios_linker_loader_add_pointer: ask guest to patch address in
 * destination file with a pointer to source file
249
 *
250
 * @linker: linker object instance
251
 * @dest_file: destination file that must be changed
252 253 254 255 256
 * @dst_patched_offset: location within destination file blob to be patched
 *                      with the pointer to @src_file+@src_offset (i.e. source
 *                      blob allocated in guest memory + @src_offset), in bytes
 * @dst_patched_offset_size: size of the pointer to be patched
 *                      at @dst_patched_offset in @dest_file blob, in bytes
257
 * @src_file: source file who's address must be taken
258 259 260
 * @src_offset: location within source file blob to which
 *              @dest_file+@dst_patched_offset will point to after
 *              firmware's executed ADD_POINTER command
261
 */
262
void bios_linker_loader_add_pointer(BIOSLinker *linker,
M
Michael S. Tsirkin 已提交
263
                                    const char *dest_file,
264 265
                                    uint32_t dst_patched_offset,
                                    uint8_t dst_patched_size,
M
Michael S. Tsirkin 已提交
266
                                    const char *src_file,
267
                                    uint32_t src_offset)
M
Michael S. Tsirkin 已提交
268
{
269
    uint64_t le_src_offset;
M
Michael S. Tsirkin 已提交
270
    BiosLinkerLoaderEntry entry;
271 272 273 274
    const BiosLinkerFileEntry *dst_file =
        bios_linker_find_file(linker, dest_file);
    const BiosLinkerFileEntry *source_file =
        bios_linker_find_file(linker, src_file);
275

276 277 278
    assert(dst_patched_offset < dst_file->blob->len);
    assert(dst_patched_offset + dst_patched_size <= dst_file->blob->len);
    assert(src_offset < source_file->blob->len);
M
Michael S. Tsirkin 已提交
279 280 281 282 283 284 285

    memset(&entry, 0, sizeof entry);
    strncpy(entry.pointer.dest_file, dest_file,
            sizeof entry.pointer.dest_file - 1);
    strncpy(entry.pointer.src_file, src_file,
            sizeof entry.pointer.src_file - 1);
    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_POINTER);
286 287 288 289 290 291 292 293
    entry.pointer.offset = cpu_to_le32(dst_patched_offset);
    entry.pointer.size = dst_patched_size;
    assert(dst_patched_size == 1 || dst_patched_size == 2 ||
           dst_patched_size == 4 || dst_patched_size == 8);

    le_src_offset = cpu_to_le64(src_offset);
    memcpy(dst_file->blob->data + dst_patched_offset,
           &le_src_offset, dst_patched_size);
M
Michael S. Tsirkin 已提交
294

295
    g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
M
Michael S. Tsirkin 已提交
296
}
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

/*
 * bios_linker_loader_write_pointer: ask guest to write a pointer to the
 * source file into the destination file, and write it back to QEMU via
 * fw_cfg DMA.
 *
 * @linker: linker object instance
 * @dest_file: destination file that must be written
 * @dst_patched_offset: location within destination file blob to be patched
 *                      with the pointer to @src_file, in bytes
 * @dst_patched_offset_size: size of the pointer to be patched
 *                      at @dst_patched_offset in @dest_file blob, in bytes
 * @src_file: source file who's address must be taken
 * @src_offset: location within source file blob to which
 *              @dest_file+@dst_patched_offset will point to after
 *              firmware's executed WRITE_POINTER command
 */
void bios_linker_loader_write_pointer(BIOSLinker *linker,
                                    const char *dest_file,
                                    uint32_t dst_patched_offset,
                                    uint8_t dst_patched_size,
                                    const char *src_file,
                                    uint32_t src_offset)
{
    BiosLinkerLoaderEntry entry;
    const BiosLinkerFileEntry *source_file =
        bios_linker_find_file(linker, src_file);

    assert(source_file);
    assert(src_offset < source_file->blob->len);
    memset(&entry, 0, sizeof entry);
    strncpy(entry.wr_pointer.dest_file, dest_file,
            sizeof entry.wr_pointer.dest_file - 1);
    strncpy(entry.wr_pointer.src_file, src_file,
            sizeof entry.wr_pointer.src_file - 1);
    entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER);
    entry.wr_pointer.dst_offset = cpu_to_le32(dst_patched_offset);
    entry.wr_pointer.src_offset = cpu_to_le32(src_offset);
    entry.wr_pointer.size = dst_patched_size;
    assert(dst_patched_size == 1 || dst_patched_size == 2 ||
           dst_patched_size == 4 || dst_patched_size == 8);

    g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
}