/* * (C) Copyright 2015 Miao Yan * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include static bool fwcfg_present; static bool fwcfg_dma_present; static struct fw_cfg_arch_ops *fwcfg_arch_ops; static LIST_HEAD(fw_list); /* Read configuration item using fw_cfg PIO interface */ static void qemu_fwcfg_read_entry_pio(uint16_t entry, uint32_t size, void *address) { debug("qemu_fwcfg_read_entry_pio: entry 0x%x, size %u address %p\n", entry, size, address); return fwcfg_arch_ops->arch_read_pio(entry, size, address); } /* Read configuration item using fw_cfg DMA interface */ static void qemu_fwcfg_read_entry_dma(uint16_t entry, uint32_t size, void *address) { struct fw_cfg_dma_access dma; dma.length = cpu_to_be32(size); dma.address = cpu_to_be64((uintptr_t)address); dma.control = cpu_to_be32(FW_CFG_DMA_READ); /* * writting FW_CFG_INVALID will cause read operation to resume at * last offset, otherwise read will start at offset 0 */ if (entry != FW_CFG_INVALID) dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16)); barrier(); debug("qemu_fwcfg_read_entry_dma: entry 0x%x, size %u address %p, control 0x%x\n", entry, size, address, be32_to_cpu(dma.control)); fwcfg_arch_ops->arch_read_dma(&dma); } bool qemu_fwcfg_present(void) { return fwcfg_present; } bool qemu_fwcfg_dma_present(void) { return fwcfg_dma_present; } void qemu_fwcfg_read_entry(uint16_t entry, uint32_t length, void *address) { if (fwcfg_dma_present) qemu_fwcfg_read_entry_dma(entry, length, address); else qemu_fwcfg_read_entry_pio(entry, length, address); } int qemu_fwcfg_online_cpus(void) { uint16_t nb_cpus; if (!fwcfg_present) return -ENODEV; qemu_fwcfg_read_entry(FW_CFG_NB_CPUS, 2, &nb_cpus); return le16_to_cpu(nb_cpus); } int qemu_fwcfg_read_firmware_list(void) { int i; uint32_t count; struct fw_file *file; struct list_head *entry; /* don't read it twice */ if (!list_empty(&fw_list)) return 0; qemu_fwcfg_read_entry(FW_CFG_FILE_DIR, 4, &count); if (!count) return 0; count = be32_to_cpu(count); for (i = 0; i < count; i++) { file = malloc(sizeof(*file)); if (!file) { printf("error: allocating resource\n"); goto err; } qemu_fwcfg_read_entry(FW_CFG_INVALID, sizeof(struct fw_cfg_file), &file->cfg); file->addr = 0; list_add_tail(&file->list, &fw_list); } return 0; err: list_for_each(entry, &fw_list) { file = list_entry(entry, struct fw_file, list); free(file); } return -ENOMEM; } struct fw_file *qemu_fwcfg_find_file(const char *name) { struct list_head *entry; struct fw_file *file; list_for_each(entry, &fw_list) { file = list_entry(entry, struct fw_file, list); if (!strcmp(file->cfg.name, name)) return file; } return NULL; } struct fw_file *qemu_fwcfg_file_iter_init(struct fw_cfg_file_iter *iter) { iter->entry = fw_list.next; return list_entry((struct list_head *)iter->entry, struct fw_file, list); } struct fw_file *qemu_fwcfg_file_iter_next(struct fw_cfg_file_iter *iter) { iter->entry = ((struct list_head *)iter->entry)->next; return list_entry((struct list_head *)iter->entry, struct fw_file, list); } bool qemu_fwcfg_file_iter_end(struct fw_cfg_file_iter *iter) { return iter->entry == &fw_list; } void qemu_fwcfg_init(struct fw_cfg_arch_ops *ops) { uint32_t qemu; uint32_t dma_enabled; fwcfg_present = false; fwcfg_dma_present = false; fwcfg_arch_ops = NULL; if (!ops || !ops->arch_read_pio || !ops->arch_read_dma) return; fwcfg_arch_ops = ops; qemu_fwcfg_read_entry_pio(FW_CFG_SIGNATURE, 4, &qemu); if (be32_to_cpu(qemu) == QEMU_FW_CFG_SIGNATURE) fwcfg_present = true; if (fwcfg_present) { qemu_fwcfg_read_entry_pio(FW_CFG_ID, 1, &dma_enabled); if (dma_enabled & FW_CFG_DMA_ENABLED) fwcfg_dma_present = true; } }