uboot/drivers/misc/qfw.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com>
   4 */
   5
   6#include <common.h>
   7#include <command.h>
   8#include <errno.h>
   9#include <log.h>
  10#include <malloc.h>
  11#include <qfw.h>
  12#include <asm/io.h>
  13#ifdef CONFIG_GENERATE_ACPI_TABLE
  14#include <asm/tables.h>
  15#endif
  16#include <linux/list.h>
  17
  18static bool fwcfg_present;
  19static bool fwcfg_dma_present;
  20static struct fw_cfg_arch_ops *fwcfg_arch_ops;
  21
  22static LIST_HEAD(fw_list);
  23
  24#ifdef CONFIG_GENERATE_ACPI_TABLE
  25/*
  26 * This function allocates memory for ACPI tables
  27 *
  28 * @entry : BIOS linker command entry which tells where to allocate memory
  29 *          (either high memory or low memory)
  30 * @addr  : The address that should be used for low memory allcation. If the
  31 *          memory allocation request is 'ZONE_HIGH' then this parameter will
  32 *          be ignored.
  33 * @return: 0 on success, or negative value on failure
  34 */
  35static int bios_linker_allocate(struct bios_linker_entry *entry, ulong *addr)
  36{
  37        uint32_t size, align;
  38        struct fw_file *file;
  39        unsigned long aligned_addr;
  40
  41        align = le32_to_cpu(entry->alloc.align);
  42        /* align must be power of 2 */
  43        if (align & (align - 1)) {
  44                printf("error: wrong alignment %u\n", align);
  45                return -EINVAL;
  46        }
  47
  48        file = qemu_fwcfg_find_file(entry->alloc.file);
  49        if (!file) {
  50                printf("error: can't find file %s\n", entry->alloc.file);
  51                return -ENOENT;
  52        }
  53
  54        size = be32_to_cpu(file->cfg.size);
  55
  56        /*
  57         * ZONE_HIGH means we need to allocate from high memory, since
  58         * malloc space is already at the end of RAM, so we directly use it.
  59         * If allocation zone is ZONE_FSEG, then we use the 'addr' passed
  60         * in which is low memory
  61         */
  62        if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH) {
  63                aligned_addr = (unsigned long)memalign(align, size);
  64                if (!aligned_addr) {
  65                        printf("error: allocating resource\n");
  66                        return -ENOMEM;
  67                }
  68        } else if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG) {
  69                aligned_addr = ALIGN(*addr, align);
  70        } else {
  71                printf("error: invalid allocation zone\n");
  72                return -EINVAL;
  73        }
  74
  75        debug("bios_linker_allocate: allocate file %s, size %u, zone %d, align %u, addr 0x%lx\n",
  76              file->cfg.name, size, entry->alloc.zone, align, aligned_addr);
  77
  78        qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select),
  79                              size, (void *)aligned_addr);
  80        file->addr = aligned_addr;
  81
  82        /* adjust address for low memory allocation */
  83        if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG)
  84                *addr = (aligned_addr + size);
  85
  86        return 0;
  87}
  88
  89/*
  90 * This function patches ACPI tables previously loaded
  91 * by bios_linker_allocate()
  92 *
  93 * @entry : BIOS linker command entry which tells how to patch
  94 *          ACPI tables
  95 * @return: 0 on success, or negative value on failure
  96 */
  97static int bios_linker_add_pointer(struct bios_linker_entry *entry)
  98{
  99        struct fw_file *dest, *src;
 100        uint32_t offset = le32_to_cpu(entry->pointer.offset);
 101        uint64_t pointer = 0;
 102
 103        dest = qemu_fwcfg_find_file(entry->pointer.dest_file);
 104        if (!dest || !dest->addr)
 105                return -ENOENT;
 106        src = qemu_fwcfg_find_file(entry->pointer.src_file);
 107        if (!src || !src->addr)
 108                return -ENOENT;
 109
 110        debug("bios_linker_add_pointer: dest->addr 0x%lx, src->addr 0x%lx, offset 0x%x size %u, 0x%llx\n",
 111              dest->addr, src->addr, offset, entry->pointer.size, pointer);
 112
 113        memcpy(&pointer, (char *)dest->addr + offset, entry->pointer.size);
 114        pointer = le64_to_cpu(pointer);
 115        pointer += (unsigned long)src->addr;
 116        pointer = cpu_to_le64(pointer);
 117        memcpy((char *)dest->addr + offset, &pointer, entry->pointer.size);
 118
 119        return 0;
 120}
 121
 122/*
 123 * This function updates checksum fields of ACPI tables previously loaded
 124 * by bios_linker_allocate()
 125 *
 126 * @entry : BIOS linker command entry which tells where to update ACPI table
 127 *          checksums
 128 * @return: 0 on success, or negative value on failure
 129 */
 130static int bios_linker_add_checksum(struct bios_linker_entry *entry)
 131{
 132        struct fw_file *file;
 133        uint8_t *data, cksum = 0;
 134        uint8_t *cksum_start;
 135
 136        file = qemu_fwcfg_find_file(entry->cksum.file);
 137        if (!file || !file->addr)
 138                return -ENOENT;
 139
 140        data = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.offset));
 141        cksum_start = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.start));
 142        cksum = table_compute_checksum(cksum_start,
 143                                       le32_to_cpu(entry->cksum.length));
 144        *data = cksum;
 145
 146        return 0;
 147}
 148
 149/* This function loads and patches ACPI tables provided by QEMU */
 150ulong write_acpi_tables(ulong addr)
 151{
 152        int i, ret = 0;
 153        struct fw_file *file;
 154        struct bios_linker_entry *table_loader;
 155        struct bios_linker_entry *entry;
 156        uint32_t size;
 157
 158        /* make sure fw_list is loaded */
 159        ret = qemu_fwcfg_read_firmware_list();
 160        if (ret) {
 161                printf("error: can't read firmware file list\n");
 162                return addr;
 163        }
 164
 165        file = qemu_fwcfg_find_file("etc/table-loader");
 166        if (!file) {
 167                printf("error: can't find etc/table-loader\n");
 168                return addr;
 169        }
 170
 171        size = be32_to_cpu(file->cfg.size);
 172        if ((size % sizeof(*entry)) != 0) {
 173                printf("error: table-loader maybe corrupted\n");
 174                return addr;
 175        }
 176
 177        table_loader = malloc(size);
 178        if (!table_loader) {
 179                printf("error: no memory for table-loader\n");
 180                return addr;
 181        }
 182
 183        qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select),
 184                              size, table_loader);
 185
 186        for (i = 0; i < (size / sizeof(*entry)); i++) {
 187                entry = table_loader + i;
 188                switch (le32_to_cpu(entry->command)) {
 189                case BIOS_LINKER_LOADER_COMMAND_ALLOCATE:
 190                        ret = bios_linker_allocate(entry, &addr);
 191                        if (ret)
 192                                goto out;
 193                        break;
 194                case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER:
 195                        ret = bios_linker_add_pointer(entry);
 196                        if (ret)
 197                                goto out;
 198                        break;
 199                case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM:
 200                        ret = bios_linker_add_checksum(entry);
 201                        if (ret)
 202                                goto out;
 203                        break;
 204                default:
 205                        break;
 206                }
 207        }
 208
 209out:
 210        if (ret) {
 211                struct fw_cfg_file_iter iter;
 212                for (file = qemu_fwcfg_file_iter_init(&iter);
 213                     !qemu_fwcfg_file_iter_end(&iter);
 214                     file = qemu_fwcfg_file_iter_next(&iter)) {
 215                        if (file->addr) {
 216                                free((void *)file->addr);
 217                                file->addr = 0;
 218                        }
 219                }
 220        }
 221
 222        free(table_loader);
 223        return addr;
 224}
 225
 226ulong acpi_get_rsdp_addr(void)
 227{
 228        struct fw_file *file;
 229
 230        file = qemu_fwcfg_find_file("etc/acpi/rsdp");
 231        return file->addr;
 232}
 233#endif
 234
 235/* Read configuration item using fw_cfg PIO interface */
 236static void qemu_fwcfg_read_entry_pio(uint16_t entry,
 237                uint32_t size, void *address)
 238{
 239        debug("qemu_fwcfg_read_entry_pio: entry 0x%x, size %u address %p\n",
 240              entry, size, address);
 241
 242        return fwcfg_arch_ops->arch_read_pio(entry, size, address);
 243}
 244
 245/* Read configuration item using fw_cfg DMA interface */
 246static void qemu_fwcfg_read_entry_dma(uint16_t entry,
 247                uint32_t size, void *address)
 248{
 249        struct fw_cfg_dma_access dma;
 250
 251        dma.length = cpu_to_be32(size);
 252        dma.address = cpu_to_be64((uintptr_t)address);
 253        dma.control = cpu_to_be32(FW_CFG_DMA_READ);
 254
 255        /*
 256         * writting FW_CFG_INVALID will cause read operation to resume at
 257         * last offset, otherwise read will start at offset 0
 258         */
 259        if (entry != FW_CFG_INVALID)
 260                dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16));
 261
 262        barrier();
 263
 264        debug("qemu_fwcfg_read_entry_dma: entry 0x%x, size %u address %p, control 0x%x\n",
 265              entry, size, address, be32_to_cpu(dma.control));
 266
 267        fwcfg_arch_ops->arch_read_dma(&dma);
 268}
 269
 270bool qemu_fwcfg_present(void)
 271{
 272        return fwcfg_present;
 273}
 274
 275bool qemu_fwcfg_dma_present(void)
 276{
 277        return fwcfg_dma_present;
 278}
 279
 280void qemu_fwcfg_read_entry(uint16_t entry, uint32_t length, void *address)
 281{
 282        if (fwcfg_dma_present)
 283                qemu_fwcfg_read_entry_dma(entry, length, address);
 284        else
 285                qemu_fwcfg_read_entry_pio(entry, length, address);
 286}
 287
 288int qemu_fwcfg_online_cpus(void)
 289{
 290        uint16_t nb_cpus;
 291
 292        if (!fwcfg_present)
 293                return -ENODEV;
 294
 295        qemu_fwcfg_read_entry(FW_CFG_NB_CPUS, 2, &nb_cpus);
 296
 297        return le16_to_cpu(nb_cpus);
 298}
 299
 300int qemu_fwcfg_read_firmware_list(void)
 301{
 302        int i;
 303        uint32_t count;
 304        struct fw_file *file;
 305        struct list_head *entry;
 306
 307        /* don't read it twice */
 308        if (!list_empty(&fw_list))
 309                return 0;
 310
 311        qemu_fwcfg_read_entry(FW_CFG_FILE_DIR, 4, &count);
 312        if (!count)
 313                return 0;
 314
 315        count = be32_to_cpu(count);
 316        for (i = 0; i < count; i++) {
 317                file = malloc(sizeof(*file));
 318                if (!file) {
 319                        printf("error: allocating resource\n");
 320                        goto err;
 321                }
 322                qemu_fwcfg_read_entry(FW_CFG_INVALID,
 323                                      sizeof(struct fw_cfg_file), &file->cfg);
 324                file->addr = 0;
 325                list_add_tail(&file->list, &fw_list);
 326        }
 327
 328        return 0;
 329
 330err:
 331        list_for_each(entry, &fw_list) {
 332                file = list_entry(entry, struct fw_file, list);
 333                free(file);
 334        }
 335
 336        return -ENOMEM;
 337}
 338
 339struct fw_file *qemu_fwcfg_find_file(const char *name)
 340{
 341        struct list_head *entry;
 342        struct fw_file *file;
 343
 344        list_for_each(entry, &fw_list) {
 345                file = list_entry(entry, struct fw_file, list);
 346                if (!strcmp(file->cfg.name, name))
 347                        return file;
 348        }
 349
 350        return NULL;
 351}
 352
 353struct fw_file *qemu_fwcfg_file_iter_init(struct fw_cfg_file_iter *iter)
 354{
 355        iter->entry = fw_list.next;
 356        return list_entry((struct list_head *)iter->entry,
 357                          struct fw_file, list);
 358}
 359
 360struct fw_file *qemu_fwcfg_file_iter_next(struct fw_cfg_file_iter *iter)
 361{
 362        iter->entry = ((struct list_head *)iter->entry)->next;
 363        return list_entry((struct list_head *)iter->entry,
 364                          struct fw_file, list);
 365}
 366
 367bool qemu_fwcfg_file_iter_end(struct fw_cfg_file_iter *iter)
 368{
 369        return iter->entry == &fw_list;
 370}
 371
 372void qemu_fwcfg_init(struct fw_cfg_arch_ops *ops)
 373{
 374        uint32_t qemu;
 375        uint32_t dma_enabled;
 376
 377        fwcfg_present = false;
 378        fwcfg_dma_present = false;
 379        fwcfg_arch_ops = NULL;
 380
 381        if (!ops || !ops->arch_read_pio || !ops->arch_read_dma)
 382                return;
 383        fwcfg_arch_ops = ops;
 384
 385        qemu_fwcfg_read_entry_pio(FW_CFG_SIGNATURE, 4, &qemu);
 386        if (be32_to_cpu(qemu) == QEMU_FW_CFG_SIGNATURE)
 387                fwcfg_present = true;
 388
 389        if (fwcfg_present) {
 390                qemu_fwcfg_read_entry_pio(FW_CFG_ID, 1, &dma_enabled);
 391                if (dma_enabled & FW_CFG_DMA_ENABLED)
 392                        fwcfg_dma_present = true;
 393        }
 394}
 395