Commit dd6f3abbb81d4d0f8883a523e26fd45833a6b0d3
Committed by
Bin Meng
1 parent
49d929bbc4
Exists in
v2017.01-smarct4x
and in
25 other branches
x86: qemu: Move qfw command over to cmd and add Kconfig entry
- Move the command portion of arch/x86/cpu/qemu/fw_cfg.c into cmd/qemu_fw_cfg.c - Move arch/x86/include/asm/fw_cfg.h to include/qemu_fw_cfg.h - Rename ACPI table portion to arch/x86/cpu/qemu/acpi_table.c Signed-off-by: Tom Rini <trini@konsulko.com> Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Showing 13 changed files with 763 additions and 732 deletions Side-by-side Diff
- arch/x86/cpu/mp_init.c
- arch/x86/cpu/qemu/Makefile
- arch/x86/cpu/qemu/acpi_table.c
- arch/x86/cpu/qemu/cpu.c
- arch/x86/cpu/qemu/fw_cfg.c
- arch/x86/cpu/qemu/qemu.c
- arch/x86/include/asm/fw_cfg.h
- arch/x86/lib/acpi_table.c
- cmd/Kconfig
- cmd/Makefile
- cmd/qemu_fw_cfg.c
- configs/qemu-x86_defconfig
- include/qemu_fw_cfg.h
arch/x86/cpu/mp_init.c
... | ... | @@ -11,6 +11,7 @@ |
11 | 11 | #include <dm.h> |
12 | 12 | #include <errno.h> |
13 | 13 | #include <malloc.h> |
14 | +#include <qemu_fw_cfg.h> | |
14 | 15 | #include <asm/atomic.h> |
15 | 16 | #include <asm/cpu.h> |
16 | 17 | #include <asm/interrupt.h> |
... | ... | @@ -21,7 +22,6 @@ |
21 | 22 | #include <asm/mtrr.h> |
22 | 23 | #include <asm/processor.h> |
23 | 24 | #include <asm/sipi.h> |
24 | -#include <asm/fw_cfg.h> | |
25 | 25 | #include <dm/device-internal.h> |
26 | 26 | #include <dm/uclass-internal.h> |
27 | 27 | #include <dm/lists.h> |
arch/x86/cpu/qemu/Makefile
arch/x86/cpu/qemu/acpi_table.c
1 | +/* | |
2 | + * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com> | |
3 | + * | |
4 | + * SPDX-License-Identifier: GPL-2.0+ | |
5 | + */ | |
6 | + | |
7 | +#include <common.h> | |
8 | +#include <command.h> | |
9 | +#include <errno.h> | |
10 | +#include <malloc.h> | |
11 | +#include <qemu_fw_cfg.h> | |
12 | +#include <asm/io.h> | |
13 | +#include <asm/tables.h> | |
14 | +#include <asm/e820.h> | |
15 | +#include <linux/list.h> | |
16 | +#include <memalign.h> | |
17 | + | |
18 | +/* | |
19 | + * This function allocates memory for ACPI tables | |
20 | + * | |
21 | + * @entry : BIOS linker command entry which tells where to allocate memory | |
22 | + * (either high memory or low memory) | |
23 | + * @addr : The address that should be used for low memory allcation. If the | |
24 | + * memory allocation request is 'ZONE_HIGH' then this parameter will | |
25 | + * be ignored. | |
26 | + * @return: 0 on success, or negative value on failure | |
27 | + */ | |
28 | +static int bios_linker_allocate(struct bios_linker_entry *entry, u32 *addr) | |
29 | +{ | |
30 | + uint32_t size, align; | |
31 | + struct fw_file *file; | |
32 | + unsigned long aligned_addr; | |
33 | + | |
34 | + align = le32_to_cpu(entry->alloc.align); | |
35 | + /* align must be power of 2 */ | |
36 | + if (align & (align - 1)) { | |
37 | + printf("error: wrong alignment %u\n", align); | |
38 | + return -EINVAL; | |
39 | + } | |
40 | + | |
41 | + file = qemu_fwcfg_find_file(entry->alloc.file); | |
42 | + if (!file) { | |
43 | + printf("error: can't find file %s\n", entry->alloc.file); | |
44 | + return -ENOENT; | |
45 | + } | |
46 | + | |
47 | + size = be32_to_cpu(file->cfg.size); | |
48 | + | |
49 | + /* | |
50 | + * ZONE_HIGH means we need to allocate from high memory, since | |
51 | + * malloc space is already at the end of RAM, so we directly use it. | |
52 | + * If allocation zone is ZONE_FSEG, then we use the 'addr' passed | |
53 | + * in which is low memory | |
54 | + */ | |
55 | + if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH) { | |
56 | + aligned_addr = (unsigned long)memalign(align, size); | |
57 | + if (!aligned_addr) { | |
58 | + printf("error: allocating resource\n"); | |
59 | + return -ENOMEM; | |
60 | + } | |
61 | + } else if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG) { | |
62 | + aligned_addr = ALIGN(*addr, align); | |
63 | + } else { | |
64 | + printf("error: invalid allocation zone\n"); | |
65 | + return -EINVAL; | |
66 | + } | |
67 | + | |
68 | + debug("bios_linker_allocate: allocate file %s, size %u, zone %d, align %u, addr 0x%lx\n", | |
69 | + file->cfg.name, size, entry->alloc.zone, align, aligned_addr); | |
70 | + | |
71 | + qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select), | |
72 | + size, (void *)aligned_addr); | |
73 | + file->addr = aligned_addr; | |
74 | + | |
75 | + /* adjust address for low memory allocation */ | |
76 | + if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG) | |
77 | + *addr = (aligned_addr + size); | |
78 | + | |
79 | + return 0; | |
80 | +} | |
81 | + | |
82 | +/* | |
83 | + * This function patches ACPI tables previously loaded | |
84 | + * by bios_linker_allocate() | |
85 | + * | |
86 | + * @entry : BIOS linker command entry which tells how to patch | |
87 | + * ACPI tables | |
88 | + * @return: 0 on success, or negative value on failure | |
89 | + */ | |
90 | +static int bios_linker_add_pointer(struct bios_linker_entry *entry) | |
91 | +{ | |
92 | + struct fw_file *dest, *src; | |
93 | + uint32_t offset = le32_to_cpu(entry->pointer.offset); | |
94 | + uint64_t pointer = 0; | |
95 | + | |
96 | + dest = qemu_fwcfg_find_file(entry->pointer.dest_file); | |
97 | + if (!dest || !dest->addr) | |
98 | + return -ENOENT; | |
99 | + src = qemu_fwcfg_find_file(entry->pointer.src_file); | |
100 | + if (!src || !src->addr) | |
101 | + return -ENOENT; | |
102 | + | |
103 | + debug("bios_linker_add_pointer: dest->addr 0x%lx, src->addr 0x%lx, offset 0x%x size %u, 0x%llx\n", | |
104 | + dest->addr, src->addr, offset, entry->pointer.size, pointer); | |
105 | + | |
106 | + memcpy(&pointer, (char *)dest->addr + offset, entry->pointer.size); | |
107 | + pointer = le64_to_cpu(pointer); | |
108 | + pointer += (unsigned long)src->addr; | |
109 | + pointer = cpu_to_le64(pointer); | |
110 | + memcpy((char *)dest->addr + offset, &pointer, entry->pointer.size); | |
111 | + | |
112 | + return 0; | |
113 | +} | |
114 | + | |
115 | +/* | |
116 | + * This function updates checksum fields of ACPI tables previously loaded | |
117 | + * by bios_linker_allocate() | |
118 | + * | |
119 | + * @entry : BIOS linker command entry which tells where to update ACPI table | |
120 | + * checksums | |
121 | + * @return: 0 on success, or negative value on failure | |
122 | + */ | |
123 | +static int bios_linker_add_checksum(struct bios_linker_entry *entry) | |
124 | +{ | |
125 | + struct fw_file *file; | |
126 | + uint8_t *data, cksum = 0; | |
127 | + uint8_t *cksum_start; | |
128 | + | |
129 | + file = qemu_fwcfg_find_file(entry->cksum.file); | |
130 | + if (!file || !file->addr) | |
131 | + return -ENOENT; | |
132 | + | |
133 | + data = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.offset)); | |
134 | + cksum_start = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.start)); | |
135 | + cksum = table_compute_checksum(cksum_start, | |
136 | + le32_to_cpu(entry->cksum.length)); | |
137 | + *data = cksum; | |
138 | + | |
139 | + return 0; | |
140 | +} | |
141 | + | |
142 | +unsigned install_e820_map(unsigned max_entries, struct e820entry *entries) | |
143 | +{ | |
144 | + entries[0].addr = 0; | |
145 | + entries[0].size = ISA_START_ADDRESS; | |
146 | + entries[0].type = E820_RAM; | |
147 | + | |
148 | + entries[1].addr = ISA_START_ADDRESS; | |
149 | + entries[1].size = ISA_END_ADDRESS - ISA_START_ADDRESS; | |
150 | + entries[1].type = E820_RESERVED; | |
151 | + | |
152 | + /* | |
153 | + * since we use memalign(malloc) to allocate high memory for | |
154 | + * storing ACPI tables, we need to reserve them in e820 tables, | |
155 | + * otherwise kernel will reclaim them and data will be corrupted | |
156 | + */ | |
157 | + entries[2].addr = ISA_END_ADDRESS; | |
158 | + entries[2].size = gd->relocaddr - TOTAL_MALLOC_LEN - ISA_END_ADDRESS; | |
159 | + entries[2].type = E820_RAM; | |
160 | + | |
161 | + /* for simplicity, reserve entire malloc space */ | |
162 | + entries[3].addr = gd->relocaddr - TOTAL_MALLOC_LEN; | |
163 | + entries[3].size = TOTAL_MALLOC_LEN; | |
164 | + entries[3].type = E820_RESERVED; | |
165 | + | |
166 | + entries[4].addr = gd->relocaddr; | |
167 | + entries[4].size = gd->ram_size - gd->relocaddr; | |
168 | + entries[4].type = E820_RESERVED; | |
169 | + | |
170 | + entries[5].addr = CONFIG_PCIE_ECAM_BASE; | |
171 | + entries[5].size = CONFIG_PCIE_ECAM_SIZE; | |
172 | + entries[5].type = E820_RESERVED; | |
173 | + | |
174 | + return 6; | |
175 | +} | |
176 | + | |
177 | +/* This function loads and patches ACPI tables provided by QEMU */ | |
178 | +u32 write_acpi_tables(u32 addr) | |
179 | +{ | |
180 | + int i, ret = 0; | |
181 | + struct fw_file *file; | |
182 | + struct bios_linker_entry *table_loader; | |
183 | + struct bios_linker_entry *entry; | |
184 | + uint32_t size; | |
185 | + | |
186 | + /* make sure fw_list is loaded */ | |
187 | + ret = qemu_fwcfg_read_firmware_list(); | |
188 | + if (ret) { | |
189 | + printf("error: can't read firmware file list\n"); | |
190 | + return addr; | |
191 | + } | |
192 | + | |
193 | + file = qemu_fwcfg_find_file("etc/table-loader"); | |
194 | + if (!file) { | |
195 | + printf("error: can't find etc/table-loader\n"); | |
196 | + return addr; | |
197 | + } | |
198 | + | |
199 | + size = be32_to_cpu(file->cfg.size); | |
200 | + if ((size % sizeof(*entry)) != 0) { | |
201 | + printf("error: table-loader maybe corrupted\n"); | |
202 | + return addr; | |
203 | + } | |
204 | + | |
205 | + table_loader = malloc(size); | |
206 | + if (!table_loader) { | |
207 | + printf("error: no memory for table-loader\n"); | |
208 | + return addr; | |
209 | + } | |
210 | + | |
211 | + qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select), | |
212 | + size, table_loader); | |
213 | + | |
214 | + for (i = 0; i < (size / sizeof(*entry)); i++) { | |
215 | + entry = table_loader + i; | |
216 | + switch (le32_to_cpu(entry->command)) { | |
217 | + case BIOS_LINKER_LOADER_COMMAND_ALLOCATE: | |
218 | + ret = bios_linker_allocate(entry, &addr); | |
219 | + if (ret) | |
220 | + goto out; | |
221 | + break; | |
222 | + case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER: | |
223 | + ret = bios_linker_add_pointer(entry); | |
224 | + if (ret) | |
225 | + goto out; | |
226 | + break; | |
227 | + case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM: | |
228 | + ret = bios_linker_add_checksum(entry); | |
229 | + if (ret) | |
230 | + goto out; | |
231 | + break; | |
232 | + default: | |
233 | + break; | |
234 | + } | |
235 | + } | |
236 | + | |
237 | +out: | |
238 | + if (ret) | |
239 | + qemu_fwcfg_free_files(); | |
240 | + | |
241 | + free(table_loader); | |
242 | + return addr; | |
243 | +} |
arch/x86/cpu/qemu/cpu.c
arch/x86/cpu/qemu/fw_cfg.c
1 | -/* | |
2 | - * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com> | |
3 | - * | |
4 | - * SPDX-License-Identifier: GPL-2.0+ | |
5 | - */ | |
6 | - | |
7 | -#include <common.h> | |
8 | -#include <command.h> | |
9 | -#include <errno.h> | |
10 | -#include <malloc.h> | |
11 | -#include <asm/io.h> | |
12 | -#include <asm/fw_cfg.h> | |
13 | -#include <asm/tables.h> | |
14 | -#include <asm/e820.h> | |
15 | -#include <linux/list.h> | |
16 | -#include <memalign.h> | |
17 | - | |
18 | -static bool fwcfg_present; | |
19 | -static bool fwcfg_dma_present; | |
20 | - | |
21 | -static LIST_HEAD(fw_list); | |
22 | - | |
23 | -/* Read configuration item using fw_cfg PIO interface */ | |
24 | -static void qemu_fwcfg_read_entry_pio(uint16_t entry, | |
25 | - uint32_t size, void *address) | |
26 | -{ | |
27 | - uint32_t i = 0; | |
28 | - uint8_t *data = address; | |
29 | - | |
30 | - /* | |
31 | - * writting FW_CFG_INVALID will cause read operation to resume at | |
32 | - * last offset, otherwise read will start at offset 0 | |
33 | - */ | |
34 | - if (entry != FW_CFG_INVALID) | |
35 | - outw(entry, FW_CONTROL_PORT); | |
36 | - while (size--) | |
37 | - data[i++] = inb(FW_DATA_PORT); | |
38 | -} | |
39 | - | |
40 | -/* Read configuration item using fw_cfg DMA interface */ | |
41 | -static void qemu_fwcfg_read_entry_dma(uint16_t entry, | |
42 | - uint32_t size, void *address) | |
43 | -{ | |
44 | - struct fw_cfg_dma_access dma; | |
45 | - | |
46 | - dma.length = cpu_to_be32(size); | |
47 | - dma.address = cpu_to_be64((uintptr_t)address); | |
48 | - dma.control = cpu_to_be32(FW_CFG_DMA_READ); | |
49 | - | |
50 | - /* | |
51 | - * writting FW_CFG_INVALID will cause read operation to resume at | |
52 | - * last offset, otherwise read will start at offset 0 | |
53 | - */ | |
54 | - if (entry != FW_CFG_INVALID) | |
55 | - dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16)); | |
56 | - | |
57 | - barrier(); | |
58 | - | |
59 | - debug("qemu_fwcfg_dma_read_entry: addr %p, length %u control 0x%x\n", | |
60 | - address, size, be32_to_cpu(dma.control)); | |
61 | - | |
62 | - outl(cpu_to_be32((uint32_t)&dma), FW_DMA_PORT_HIGH); | |
63 | - | |
64 | - while (be32_to_cpu(dma.control) & ~FW_CFG_DMA_ERROR) | |
65 | - __asm__ __volatile__ ("pause"); | |
66 | -} | |
67 | - | |
68 | -static bool qemu_fwcfg_present(void) | |
69 | -{ | |
70 | - uint32_t qemu; | |
71 | - | |
72 | - qemu_fwcfg_read_entry_pio(FW_CFG_SIGNATURE, 4, &qemu); | |
73 | - return be32_to_cpu(qemu) == QEMU_FW_CFG_SIGNATURE; | |
74 | -} | |
75 | - | |
76 | -static bool qemu_fwcfg_dma_present(void) | |
77 | -{ | |
78 | - uint8_t dma_enabled; | |
79 | - | |
80 | - qemu_fwcfg_read_entry_pio(FW_CFG_ID, 1, &dma_enabled); | |
81 | - if (dma_enabled & FW_CFG_DMA_ENABLED) | |
82 | - return true; | |
83 | - | |
84 | - return false; | |
85 | -} | |
86 | - | |
87 | -static void qemu_fwcfg_read_entry(uint16_t entry, | |
88 | - uint32_t length, void *address) | |
89 | -{ | |
90 | - if (fwcfg_dma_present) | |
91 | - qemu_fwcfg_read_entry_dma(entry, length, address); | |
92 | - else | |
93 | - qemu_fwcfg_read_entry_pio(entry, length, address); | |
94 | -} | |
95 | - | |
96 | -int qemu_fwcfg_online_cpus(void) | |
97 | -{ | |
98 | - uint16_t nb_cpus; | |
99 | - | |
100 | - if (!fwcfg_present) | |
101 | - return -ENODEV; | |
102 | - | |
103 | - qemu_fwcfg_read_entry(FW_CFG_NB_CPUS, 2, &nb_cpus); | |
104 | - | |
105 | - return le16_to_cpu(nb_cpus); | |
106 | -} | |
107 | - | |
108 | -/* | |
109 | - * This function prepares kernel for zboot. It loads kernel data | |
110 | - * to 'load_addr', initrd to 'initrd_addr' and kernel command | |
111 | - * line using qemu fw_cfg interface. | |
112 | - */ | |
113 | -static int qemu_fwcfg_setup_kernel(void *load_addr, void *initrd_addr) | |
114 | -{ | |
115 | - char *data_addr; | |
116 | - uint32_t setup_size, kernel_size, cmdline_size, initrd_size; | |
117 | - | |
118 | - qemu_fwcfg_read_entry(FW_CFG_SETUP_SIZE, 4, &setup_size); | |
119 | - qemu_fwcfg_read_entry(FW_CFG_KERNEL_SIZE, 4, &kernel_size); | |
120 | - | |
121 | - if (setup_size == 0 || kernel_size == 0) { | |
122 | - printf("warning: no kernel available\n"); | |
123 | - return -1; | |
124 | - } | |
125 | - | |
126 | - data_addr = load_addr; | |
127 | - qemu_fwcfg_read_entry(FW_CFG_SETUP_DATA, | |
128 | - le32_to_cpu(setup_size), data_addr); | |
129 | - data_addr += le32_to_cpu(setup_size); | |
130 | - | |
131 | - qemu_fwcfg_read_entry(FW_CFG_KERNEL_DATA, | |
132 | - le32_to_cpu(kernel_size), data_addr); | |
133 | - data_addr += le32_to_cpu(kernel_size); | |
134 | - | |
135 | - data_addr = initrd_addr; | |
136 | - qemu_fwcfg_read_entry(FW_CFG_INITRD_SIZE, 4, &initrd_size); | |
137 | - if (initrd_size == 0) { | |
138 | - printf("warning: no initrd available\n"); | |
139 | - } else { | |
140 | - qemu_fwcfg_read_entry(FW_CFG_INITRD_DATA, | |
141 | - le32_to_cpu(initrd_size), data_addr); | |
142 | - data_addr += le32_to_cpu(initrd_size); | |
143 | - } | |
144 | - | |
145 | - qemu_fwcfg_read_entry(FW_CFG_CMDLINE_SIZE, 4, &cmdline_size); | |
146 | - if (cmdline_size) { | |
147 | - qemu_fwcfg_read_entry(FW_CFG_CMDLINE_DATA, | |
148 | - le32_to_cpu(cmdline_size), data_addr); | |
149 | - /* | |
150 | - * if kernel cmdline only contains '\0', (e.g. no -append | |
151 | - * when invoking qemu), do not update bootargs | |
152 | - */ | |
153 | - if (*data_addr != '\0') { | |
154 | - if (setenv("bootargs", data_addr) < 0) | |
155 | - printf("warning: unable to change bootargs\n"); | |
156 | - } | |
157 | - } | |
158 | - | |
159 | - printf("loading kernel to address %p size %x", load_addr, | |
160 | - le32_to_cpu(kernel_size)); | |
161 | - if (initrd_size) | |
162 | - printf(" initrd %p size %x\n", | |
163 | - initrd_addr, | |
164 | - le32_to_cpu(initrd_size)); | |
165 | - else | |
166 | - printf("\n"); | |
167 | - | |
168 | - return 0; | |
169 | -} | |
170 | - | |
171 | -static int qemu_fwcfg_read_firmware_list(void) | |
172 | -{ | |
173 | - int i; | |
174 | - uint32_t count; | |
175 | - struct fw_file *file; | |
176 | - struct list_head *entry; | |
177 | - | |
178 | - /* don't read it twice */ | |
179 | - if (!list_empty(&fw_list)) | |
180 | - return 0; | |
181 | - | |
182 | - qemu_fwcfg_read_entry(FW_CFG_FILE_DIR, 4, &count); | |
183 | - if (!count) | |
184 | - return 0; | |
185 | - | |
186 | - count = be32_to_cpu(count); | |
187 | - for (i = 0; i < count; i++) { | |
188 | - file = malloc(sizeof(*file)); | |
189 | - if (!file) { | |
190 | - printf("error: allocating resource\n"); | |
191 | - goto err; | |
192 | - } | |
193 | - qemu_fwcfg_read_entry(FW_CFG_INVALID, | |
194 | - sizeof(struct fw_cfg_file), &file->cfg); | |
195 | - file->addr = 0; | |
196 | - list_add_tail(&file->list, &fw_list); | |
197 | - } | |
198 | - | |
199 | - return 0; | |
200 | - | |
201 | -err: | |
202 | - list_for_each(entry, &fw_list) { | |
203 | - file = list_entry(entry, struct fw_file, list); | |
204 | - free(file); | |
205 | - } | |
206 | - | |
207 | - return -ENOMEM; | |
208 | -} | |
209 | - | |
210 | -#ifdef CONFIG_QEMU_ACPI_TABLE | |
211 | -static struct fw_file *qemu_fwcfg_find_file(const char *name) | |
212 | -{ | |
213 | - struct list_head *entry; | |
214 | - struct fw_file *file; | |
215 | - | |
216 | - list_for_each(entry, &fw_list) { | |
217 | - file = list_entry(entry, struct fw_file, list); | |
218 | - if (!strcmp(file->cfg.name, name)) | |
219 | - return file; | |
220 | - } | |
221 | - | |
222 | - return NULL; | |
223 | -} | |
224 | - | |
225 | -/* | |
226 | - * This function allocates memory for ACPI tables | |
227 | - * | |
228 | - * @entry : BIOS linker command entry which tells where to allocate memory | |
229 | - * (either high memory or low memory) | |
230 | - * @addr : The address that should be used for low memory allcation. If the | |
231 | - * memory allocation request is 'ZONE_HIGH' then this parameter will | |
232 | - * be ignored. | |
233 | - * @return: 0 on success, or negative value on failure | |
234 | - */ | |
235 | -static int bios_linker_allocate(struct bios_linker_entry *entry, u32 *addr) | |
236 | -{ | |
237 | - uint32_t size, align; | |
238 | - struct fw_file *file; | |
239 | - unsigned long aligned_addr; | |
240 | - | |
241 | - align = le32_to_cpu(entry->alloc.align); | |
242 | - /* align must be power of 2 */ | |
243 | - if (align & (align - 1)) { | |
244 | - printf("error: wrong alignment %u\n", align); | |
245 | - return -EINVAL; | |
246 | - } | |
247 | - | |
248 | - file = qemu_fwcfg_find_file(entry->alloc.file); | |
249 | - if (!file) { | |
250 | - printf("error: can't find file %s\n", entry->alloc.file); | |
251 | - return -ENOENT; | |
252 | - } | |
253 | - | |
254 | - size = be32_to_cpu(file->cfg.size); | |
255 | - | |
256 | - /* | |
257 | - * ZONE_HIGH means we need to allocate from high memory, since | |
258 | - * malloc space is already at the end of RAM, so we directly use it. | |
259 | - * If allocation zone is ZONE_FSEG, then we use the 'addr' passed | |
260 | - * in which is low memory | |
261 | - */ | |
262 | - if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH) { | |
263 | - aligned_addr = (unsigned long)memalign(align, size); | |
264 | - if (!aligned_addr) { | |
265 | - printf("error: allocating resource\n"); | |
266 | - return -ENOMEM; | |
267 | - } | |
268 | - } else if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG) { | |
269 | - aligned_addr = ALIGN(*addr, align); | |
270 | - } else { | |
271 | - printf("error: invalid allocation zone\n"); | |
272 | - return -EINVAL; | |
273 | - } | |
274 | - | |
275 | - debug("bios_linker_allocate: allocate file %s, size %u, zone %d, align %u, addr 0x%lx\n", | |
276 | - file->cfg.name, size, entry->alloc.zone, align, aligned_addr); | |
277 | - | |
278 | - qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select), | |
279 | - size, (void *)aligned_addr); | |
280 | - file->addr = aligned_addr; | |
281 | - | |
282 | - /* adjust address for low memory allocation */ | |
283 | - if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG) | |
284 | - *addr = (aligned_addr + size); | |
285 | - | |
286 | - return 0; | |
287 | -} | |
288 | - | |
289 | -/* | |
290 | - * This function patches ACPI tables previously loaded | |
291 | - * by bios_linker_allocate() | |
292 | - * | |
293 | - * @entry : BIOS linker command entry which tells how to patch | |
294 | - * ACPI tables | |
295 | - * @return: 0 on success, or negative value on failure | |
296 | - */ | |
297 | -static int bios_linker_add_pointer(struct bios_linker_entry *entry) | |
298 | -{ | |
299 | - struct fw_file *dest, *src; | |
300 | - uint32_t offset = le32_to_cpu(entry->pointer.offset); | |
301 | - uint64_t pointer = 0; | |
302 | - | |
303 | - dest = qemu_fwcfg_find_file(entry->pointer.dest_file); | |
304 | - if (!dest || !dest->addr) | |
305 | - return -ENOENT; | |
306 | - src = qemu_fwcfg_find_file(entry->pointer.src_file); | |
307 | - if (!src || !src->addr) | |
308 | - return -ENOENT; | |
309 | - | |
310 | - debug("bios_linker_add_pointer: dest->addr 0x%lx, src->addr 0x%lx, offset 0x%x size %u, 0x%llx\n", | |
311 | - dest->addr, src->addr, offset, entry->pointer.size, pointer); | |
312 | - | |
313 | - memcpy(&pointer, (char *)dest->addr + offset, entry->pointer.size); | |
314 | - pointer = le64_to_cpu(pointer); | |
315 | - pointer += (unsigned long)src->addr; | |
316 | - pointer = cpu_to_le64(pointer); | |
317 | - memcpy((char *)dest->addr + offset, &pointer, entry->pointer.size); | |
318 | - | |
319 | - return 0; | |
320 | -} | |
321 | - | |
322 | -/* | |
323 | - * This function updates checksum fields of ACPI tables previously loaded | |
324 | - * by bios_linker_allocate() | |
325 | - * | |
326 | - * @entry : BIOS linker command entry which tells where to update ACPI table | |
327 | - * checksums | |
328 | - * @return: 0 on success, or negative value on failure | |
329 | - */ | |
330 | -static int bios_linker_add_checksum(struct bios_linker_entry *entry) | |
331 | -{ | |
332 | - struct fw_file *file; | |
333 | - uint8_t *data, cksum = 0; | |
334 | - uint8_t *cksum_start; | |
335 | - | |
336 | - file = qemu_fwcfg_find_file(entry->cksum.file); | |
337 | - if (!file || !file->addr) | |
338 | - return -ENOENT; | |
339 | - | |
340 | - data = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.offset)); | |
341 | - cksum_start = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.start)); | |
342 | - cksum = table_compute_checksum(cksum_start, | |
343 | - le32_to_cpu(entry->cksum.length)); | |
344 | - *data = cksum; | |
345 | - | |
346 | - return 0; | |
347 | -} | |
348 | - | |
349 | -unsigned install_e820_map(unsigned max_entries, struct e820entry *entries) | |
350 | -{ | |
351 | - entries[0].addr = 0; | |
352 | - entries[0].size = ISA_START_ADDRESS; | |
353 | - entries[0].type = E820_RAM; | |
354 | - | |
355 | - entries[1].addr = ISA_START_ADDRESS; | |
356 | - entries[1].size = ISA_END_ADDRESS - ISA_START_ADDRESS; | |
357 | - entries[1].type = E820_RESERVED; | |
358 | - | |
359 | - /* | |
360 | - * since we use memalign(malloc) to allocate high memory for | |
361 | - * storing ACPI tables, we need to reserve them in e820 tables, | |
362 | - * otherwise kernel will reclaim them and data will be corrupted | |
363 | - */ | |
364 | - entries[2].addr = ISA_END_ADDRESS; | |
365 | - entries[2].size = gd->relocaddr - TOTAL_MALLOC_LEN - ISA_END_ADDRESS; | |
366 | - entries[2].type = E820_RAM; | |
367 | - | |
368 | - /* for simplicity, reserve entire malloc space */ | |
369 | - entries[3].addr = gd->relocaddr - TOTAL_MALLOC_LEN; | |
370 | - entries[3].size = TOTAL_MALLOC_LEN; | |
371 | - entries[3].type = E820_RESERVED; | |
372 | - | |
373 | - entries[4].addr = gd->relocaddr; | |
374 | - entries[4].size = gd->ram_size - gd->relocaddr; | |
375 | - entries[4].type = E820_RESERVED; | |
376 | - | |
377 | - entries[5].addr = CONFIG_PCIE_ECAM_BASE; | |
378 | - entries[5].size = CONFIG_PCIE_ECAM_SIZE; | |
379 | - entries[5].type = E820_RESERVED; | |
380 | - | |
381 | - return 6; | |
382 | -} | |
383 | - | |
384 | -/* This function loads and patches ACPI tables provided by QEMU */ | |
385 | -u32 write_acpi_tables(u32 addr) | |
386 | -{ | |
387 | - int i, ret = 0; | |
388 | - struct fw_file *file; | |
389 | - struct bios_linker_entry *table_loader; | |
390 | - struct bios_linker_entry *entry; | |
391 | - uint32_t size; | |
392 | - struct list_head *list; | |
393 | - | |
394 | - /* make sure fw_list is loaded */ | |
395 | - ret = qemu_fwcfg_read_firmware_list(); | |
396 | - if (ret) { | |
397 | - printf("error: can't read firmware file list\n"); | |
398 | - return addr; | |
399 | - } | |
400 | - | |
401 | - file = qemu_fwcfg_find_file("etc/table-loader"); | |
402 | - if (!file) { | |
403 | - printf("error: can't find etc/table-loader\n"); | |
404 | - return addr; | |
405 | - } | |
406 | - | |
407 | - size = be32_to_cpu(file->cfg.size); | |
408 | - if ((size % sizeof(*entry)) != 0) { | |
409 | - printf("error: table-loader maybe corrupted\n"); | |
410 | - return addr; | |
411 | - } | |
412 | - | |
413 | - table_loader = malloc(size); | |
414 | - if (!table_loader) { | |
415 | - printf("error: no memory for table-loader\n"); | |
416 | - return addr; | |
417 | - } | |
418 | - | |
419 | - qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select), | |
420 | - size, table_loader); | |
421 | - | |
422 | - for (i = 0; i < (size / sizeof(*entry)); i++) { | |
423 | - entry = table_loader + i; | |
424 | - switch (le32_to_cpu(entry->command)) { | |
425 | - case BIOS_LINKER_LOADER_COMMAND_ALLOCATE: | |
426 | - ret = bios_linker_allocate(entry, &addr); | |
427 | - if (ret) | |
428 | - goto out; | |
429 | - break; | |
430 | - case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER: | |
431 | - ret = bios_linker_add_pointer(entry); | |
432 | - if (ret) | |
433 | - goto out; | |
434 | - break; | |
435 | - case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM: | |
436 | - ret = bios_linker_add_checksum(entry); | |
437 | - if (ret) | |
438 | - goto out; | |
439 | - break; | |
440 | - default: | |
441 | - break; | |
442 | - } | |
443 | - } | |
444 | - | |
445 | -out: | |
446 | - if (ret) { | |
447 | - list_for_each(list, &fw_list) { | |
448 | - file = list_entry(list, struct fw_file, list); | |
449 | - if (file->addr) | |
450 | - free((void *)file->addr); | |
451 | - } | |
452 | - } | |
453 | - | |
454 | - free(table_loader); | |
455 | - return addr; | |
456 | -} | |
457 | -#endif | |
458 | - | |
459 | -static int qemu_fwcfg_list_firmware(void) | |
460 | -{ | |
461 | - int ret; | |
462 | - struct list_head *entry; | |
463 | - struct fw_file *file; | |
464 | - | |
465 | - /* make sure fw_list is loaded */ | |
466 | - ret = qemu_fwcfg_read_firmware_list(); | |
467 | - if (ret) | |
468 | - return ret; | |
469 | - | |
470 | - list_for_each(entry, &fw_list) { | |
471 | - file = list_entry(entry, struct fw_file, list); | |
472 | - printf("%-56s\n", file->cfg.name); | |
473 | - } | |
474 | - | |
475 | - return 0; | |
476 | -} | |
477 | - | |
478 | -void qemu_fwcfg_init(void) | |
479 | -{ | |
480 | - fwcfg_present = qemu_fwcfg_present(); | |
481 | - if (fwcfg_present) | |
482 | - fwcfg_dma_present = qemu_fwcfg_dma_present(); | |
483 | -} | |
484 | - | |
485 | -static int qemu_fwcfg_do_list(cmd_tbl_t *cmdtp, int flag, | |
486 | - int argc, char * const argv[]) | |
487 | -{ | |
488 | - if (qemu_fwcfg_list_firmware() < 0) | |
489 | - return CMD_RET_FAILURE; | |
490 | - | |
491 | - return 0; | |
492 | -} | |
493 | - | |
494 | -static int qemu_fwcfg_do_cpus(cmd_tbl_t *cmdtp, int flag, | |
495 | - int argc, char * const argv[]) | |
496 | -{ | |
497 | - int ret = qemu_fwcfg_online_cpus(); | |
498 | - if (ret < 0) { | |
499 | - printf("QEMU fw_cfg interface not found\n"); | |
500 | - return CMD_RET_FAILURE; | |
501 | - } | |
502 | - | |
503 | - printf("%d cpu(s) online\n", qemu_fwcfg_online_cpus()); | |
504 | - | |
505 | - return 0; | |
506 | -} | |
507 | - | |
508 | -static int qemu_fwcfg_do_load(cmd_tbl_t *cmdtp, int flag, | |
509 | - int argc, char * const argv[]) | |
510 | -{ | |
511 | - char *env; | |
512 | - void *load_addr; | |
513 | - void *initrd_addr; | |
514 | - | |
515 | - env = getenv("loadaddr"); | |
516 | - load_addr = env ? | |
517 | - (void *)simple_strtoul(env, NULL, 16) : | |
518 | - (void *)CONFIG_LOADADDR; | |
519 | - | |
520 | - env = getenv("ramdiskaddr"); | |
521 | - initrd_addr = env ? | |
522 | - (void *)simple_strtoul(env, NULL, 16) : | |
523 | - (void *)CONFIG_RAMDISK_ADDR; | |
524 | - | |
525 | - if (argc == 2) { | |
526 | - load_addr = (void *)simple_strtoul(argv[0], NULL, 16); | |
527 | - initrd_addr = (void *)simple_strtoul(argv[1], NULL, 16); | |
528 | - } else if (argc == 1) { | |
529 | - load_addr = (void *)simple_strtoul(argv[0], NULL, 16); | |
530 | - } | |
531 | - | |
532 | - return qemu_fwcfg_setup_kernel(load_addr, initrd_addr); | |
533 | -} | |
534 | - | |
535 | -static cmd_tbl_t fwcfg_commands[] = { | |
536 | - U_BOOT_CMD_MKENT(list, 0, 1, qemu_fwcfg_do_list, "", ""), | |
537 | - U_BOOT_CMD_MKENT(cpus, 0, 1, qemu_fwcfg_do_cpus, "", ""), | |
538 | - U_BOOT_CMD_MKENT(load, 2, 1, qemu_fwcfg_do_load, "", ""), | |
539 | -}; | |
540 | - | |
541 | -static int do_qemu_fw(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | |
542 | -{ | |
543 | - int ret; | |
544 | - cmd_tbl_t *fwcfg_cmd; | |
545 | - | |
546 | - if (!fwcfg_present) { | |
547 | - printf("QEMU fw_cfg interface not found\n"); | |
548 | - return CMD_RET_USAGE; | |
549 | - } | |
550 | - | |
551 | - fwcfg_cmd = find_cmd_tbl(argv[1], fwcfg_commands, | |
552 | - ARRAY_SIZE(fwcfg_commands)); | |
553 | - argc -= 2; | |
554 | - argv += 2; | |
555 | - if (!fwcfg_cmd || argc > fwcfg_cmd->maxargs) | |
556 | - return CMD_RET_USAGE; | |
557 | - | |
558 | - ret = fwcfg_cmd->cmd(fwcfg_cmd, flag, argc, argv); | |
559 | - | |
560 | - return cmd_process_error(fwcfg_cmd, ret); | |
561 | -} | |
562 | - | |
563 | -U_BOOT_CMD( | |
564 | - qfw, 4, 1, do_qemu_fw, | |
565 | - "QEMU firmware interface", | |
566 | - "<command>\n" | |
567 | - " - list : print firmware(s) currently loaded\n" | |
568 | - " - cpus : print online cpu number\n" | |
569 | - " - load <kernel addr> <initrd addr> : load kernel and initrd (if any), and setup for zboot\n" | |
570 | -) |
arch/x86/cpu/qemu/qemu.c
... | ... | @@ -6,12 +6,12 @@ |
6 | 6 | |
7 | 7 | #include <common.h> |
8 | 8 | #include <pci.h> |
9 | +#include <qemu_fw_cfg.h> | |
9 | 10 | #include <asm/irq.h> |
10 | 11 | #include <asm/post.h> |
11 | 12 | #include <asm/processor.h> |
12 | 13 | #include <asm/arch/device.h> |
13 | 14 | #include <asm/arch/qemu.h> |
14 | -#include <asm/fw_cfg.h> | |
15 | 15 | |
16 | 16 | static bool i440fx; |
17 | 17 |
arch/x86/include/asm/fw_cfg.h
1 | -/* | |
2 | - * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com> | |
3 | - * | |
4 | - * SPDX-License-Identifier: GPL-2.0+ | |
5 | - */ | |
6 | - | |
7 | -#ifndef __FW_CFG__ | |
8 | -#define __FW_CFG__ | |
9 | - | |
10 | -#define FW_CONTROL_PORT 0x510 | |
11 | -#define FW_DATA_PORT 0x511 | |
12 | -#define FW_DMA_PORT_LOW 0x514 | |
13 | -#define FW_DMA_PORT_HIGH 0x518 | |
14 | - | |
15 | -#include <linux/list.h> | |
16 | - | |
17 | -enum qemu_fwcfg_items { | |
18 | - FW_CFG_SIGNATURE = 0x00, | |
19 | - FW_CFG_ID = 0x01, | |
20 | - FW_CFG_UUID = 0x02, | |
21 | - FW_CFG_RAM_SIZE = 0x03, | |
22 | - FW_CFG_NOGRAPHIC = 0x04, | |
23 | - FW_CFG_NB_CPUS = 0x05, | |
24 | - FW_CFG_MACHINE_ID = 0x06, | |
25 | - FW_CFG_KERNEL_ADDR = 0x07, | |
26 | - FW_CFG_KERNEL_SIZE = 0x08, | |
27 | - FW_CFG_KERNEL_CMDLINE = 0x09, | |
28 | - FW_CFG_INITRD_ADDR = 0x0a, | |
29 | - FW_CFG_INITRD_SIZE = 0x0b, | |
30 | - FW_CFG_BOOT_DEVICE = 0x0c, | |
31 | - FW_CFG_NUMA = 0x0d, | |
32 | - FW_CFG_BOOT_MENU = 0x0e, | |
33 | - FW_CFG_MAX_CPUS = 0x0f, | |
34 | - FW_CFG_KERNEL_ENTRY = 0x10, | |
35 | - FW_CFG_KERNEL_DATA = 0x11, | |
36 | - FW_CFG_INITRD_DATA = 0x12, | |
37 | - FW_CFG_CMDLINE_ADDR = 0x13, | |
38 | - FW_CFG_CMDLINE_SIZE = 0x14, | |
39 | - FW_CFG_CMDLINE_DATA = 0x15, | |
40 | - FW_CFG_SETUP_ADDR = 0x16, | |
41 | - FW_CFG_SETUP_SIZE = 0x17, | |
42 | - FW_CFG_SETUP_DATA = 0x18, | |
43 | - FW_CFG_FILE_DIR = 0x19, | |
44 | - FW_CFG_FILE_FIRST = 0x20, | |
45 | - FW_CFG_WRITE_CHANNEL = 0x4000, | |
46 | - FW_CFG_ARCH_LOCAL = 0x8000, | |
47 | - FW_CFG_INVALID = 0xffff, | |
48 | -}; | |
49 | - | |
50 | -enum { | |
51 | - BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1, | |
52 | - BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2, | |
53 | - BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3, | |
54 | -}; | |
55 | - | |
56 | -enum { | |
57 | - BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1, | |
58 | - BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2, | |
59 | -}; | |
60 | - | |
61 | -#define FW_CFG_FILE_SLOTS 0x10 | |
62 | -#define FW_CFG_MAX_ENTRY (FW_CFG_FILE_FIRST + FW_CFG_FILE_SLOTS) | |
63 | -#define FW_CFG_ENTRY_MASK ~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL) | |
64 | - | |
65 | -#define FW_CFG_MAX_FILE_PATH 56 | |
66 | -#define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH | |
67 | - | |
68 | -#define QEMU_FW_CFG_SIGNATURE (('Q' << 24) | ('E' << 16) | ('M' << 8) | 'U') | |
69 | - | |
70 | -#define FW_CFG_DMA_ERROR (1 << 0) | |
71 | -#define FW_CFG_DMA_READ (1 << 1) | |
72 | -#define FW_CFG_DMA_SKIP (1 << 2) | |
73 | -#define FW_CFG_DMA_SELECT (1 << 3) | |
74 | - | |
75 | -#define FW_CFG_DMA_ENABLED (1 << 1) | |
76 | - | |
77 | -struct fw_cfg_file { | |
78 | - __be32 size; | |
79 | - __be16 select; | |
80 | - __be16 reserved; | |
81 | - char name[FW_CFG_MAX_FILE_PATH]; | |
82 | -}; | |
83 | - | |
84 | -struct fw_file { | |
85 | - struct fw_cfg_file cfg; /* firmware file information */ | |
86 | - unsigned long addr; /* firmware file in-memory address */ | |
87 | - struct list_head list; /* list node to link to fw_list */ | |
88 | -}; | |
89 | - | |
90 | -struct fw_cfg_dma_access { | |
91 | - __be32 control; | |
92 | - __be32 length; | |
93 | - __be64 address; | |
94 | -}; | |
95 | - | |
96 | -struct bios_linker_entry { | |
97 | - __le32 command; | |
98 | - union { | |
99 | - /* | |
100 | - * COMMAND_ALLOCATE - allocate a table from @alloc.file | |
101 | - * subject to @alloc.align alignment (must be power of 2) | |
102 | - * and @alloc.zone (can be HIGH or FSEG) requirements. | |
103 | - * | |
104 | - * Must appear exactly once for each file, and before | |
105 | - * this file is referenced by any other command. | |
106 | - */ | |
107 | - struct { | |
108 | - char file[BIOS_LINKER_LOADER_FILESZ]; | |
109 | - __le32 align; | |
110 | - uint8_t zone; | |
111 | - } alloc; | |
112 | - | |
113 | - /* | |
114 | - * COMMAND_ADD_POINTER - patch the table (originating from | |
115 | - * @dest_file) at @pointer.offset, by adding a pointer to the | |
116 | - * table originating from @src_file. 1,2,4 or 8 byte unsigned | |
117 | - * addition is used depending on @pointer.size. | |
118 | - */ | |
119 | - struct { | |
120 | - char dest_file[BIOS_LINKER_LOADER_FILESZ]; | |
121 | - char src_file[BIOS_LINKER_LOADER_FILESZ]; | |
122 | - __le32 offset; | |
123 | - uint8_t size; | |
124 | - } pointer; | |
125 | - | |
126 | - /* | |
127 | - * COMMAND_ADD_CHECKSUM - calculate checksum of the range | |
128 | - * specified by @cksum_start and @cksum_length fields, | |
129 | - * and then add the value at @cksum.offset. | |
130 | - * Checksum simply sums -X for each byte X in the range | |
131 | - * using 8-bit math. | |
132 | - */ | |
133 | - struct { | |
134 | - char file[BIOS_LINKER_LOADER_FILESZ]; | |
135 | - __le32 offset; | |
136 | - __le32 start; | |
137 | - __le32 length; | |
138 | - } cksum; | |
139 | - | |
140 | - /* padding */ | |
141 | - char pad[124]; | |
142 | - }; | |
143 | -} __packed; | |
144 | - | |
145 | -/** | |
146 | - * Initialize QEMU fw_cfg interface | |
147 | - */ | |
148 | -void qemu_fwcfg_init(void); | |
149 | - | |
150 | -/** | |
151 | - * Get system cpu number | |
152 | - * | |
153 | - * @return: cpu number in system | |
154 | - */ | |
155 | -int qemu_fwcfg_online_cpus(void); | |
156 | - | |
157 | -#endif |
arch/x86/lib/acpi_table.c
cmd/Kconfig
... | ... | @@ -593,6 +593,13 @@ |
593 | 593 | sound init - set up sound system |
594 | 594 | sound play - play a sound |
595 | 595 | |
596 | +config CMD_QEMU_FW_CFG | |
597 | + bool "qfw" | |
598 | + depends on X86 | |
599 | + help | |
600 | + This provides access to the QEMU firmware interface. The main | |
601 | + feature is to allow easy loading of files passed to qemu-system | |
602 | + via -kernel / -initrd | |
596 | 603 | endmenu |
597 | 604 | |
598 | 605 | config CMD_BOOTSTAGE |
cmd/Makefile
... | ... | @@ -105,6 +105,7 @@ |
105 | 105 | obj-y += pcmcia.o |
106 | 106 | obj-$(CONFIG_CMD_PORTIO) += portio.o |
107 | 107 | obj-$(CONFIG_CMD_PXE) += pxe.o |
108 | +obj-$(CONFIG_CMD_QEMU_FW_CFG) += qemu_fw_cfg.o | |
108 | 109 | obj-$(CONFIG_CMD_READ) += read.o |
109 | 110 | obj-$(CONFIG_CMD_REGINFO) += reginfo.o |
110 | 111 | obj-$(CONFIG_CMD_REISER) += reiser.o |
cmd/qemu_fw_cfg.c
1 | +/* | |
2 | + * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com> | |
3 | + * | |
4 | + * SPDX-License-Identifier: GPL-2.0+ | |
5 | + */ | |
6 | + | |
7 | +#include <common.h> | |
8 | +#include <command.h> | |
9 | +#include <errno.h> | |
10 | +#include <malloc.h> | |
11 | +#include <qemu_fw_cfg.h> | |
12 | +#include <asm/io.h> | |
13 | +#include <linux/list.h> | |
14 | + | |
15 | +static bool fwcfg_present; | |
16 | +static bool fwcfg_dma_present; | |
17 | + | |
18 | +static LIST_HEAD(fw_list); | |
19 | + | |
20 | +/* Read configuration item using fw_cfg PIO interface */ | |
21 | +static void qemu_fwcfg_read_entry_pio(uint16_t entry, | |
22 | + uint32_t size, void *address) | |
23 | +{ | |
24 | + uint32_t i = 0; | |
25 | + uint8_t *data = address; | |
26 | + | |
27 | + /* | |
28 | + * writting FW_CFG_INVALID will cause read operation to resume at | |
29 | + * last offset, otherwise read will start at offset 0 | |
30 | + */ | |
31 | + if (entry != FW_CFG_INVALID) | |
32 | + outw(entry, FW_CONTROL_PORT); | |
33 | + while (size--) | |
34 | + data[i++] = inb(FW_DATA_PORT); | |
35 | +} | |
36 | + | |
37 | +/* Read configuration item using fw_cfg DMA interface */ | |
38 | +static void qemu_fwcfg_read_entry_dma(uint16_t entry, | |
39 | + uint32_t size, void *address) | |
40 | +{ | |
41 | + struct fw_cfg_dma_access dma; | |
42 | + | |
43 | + dma.length = cpu_to_be32(size); | |
44 | + dma.address = cpu_to_be64((uintptr_t)address); | |
45 | + dma.control = cpu_to_be32(FW_CFG_DMA_READ); | |
46 | + | |
47 | + /* | |
48 | + * writting FW_CFG_INVALID will cause read operation to resume at | |
49 | + * last offset, otherwise read will start at offset 0 | |
50 | + */ | |
51 | + if (entry != FW_CFG_INVALID) | |
52 | + dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16)); | |
53 | + | |
54 | + barrier(); | |
55 | + | |
56 | + debug("qemu_fwcfg_dma_read_entry: addr %p, length %u control 0x%x\n", | |
57 | + address, size, be32_to_cpu(dma.control)); | |
58 | + | |
59 | + outl(cpu_to_be32((uint32_t)&dma), FW_DMA_PORT_HIGH); | |
60 | + | |
61 | + while (be32_to_cpu(dma.control) & ~FW_CFG_DMA_ERROR) | |
62 | + __asm__ __volatile__ ("pause"); | |
63 | +} | |
64 | + | |
65 | +static bool qemu_fwcfg_present(void) | |
66 | +{ | |
67 | + uint32_t qemu; | |
68 | + | |
69 | + qemu_fwcfg_read_entry_pio(FW_CFG_SIGNATURE, 4, &qemu); | |
70 | + return be32_to_cpu(qemu) == QEMU_FW_CFG_SIGNATURE; | |
71 | +} | |
72 | + | |
73 | +static bool qemu_fwcfg_dma_present(void) | |
74 | +{ | |
75 | + uint8_t dma_enabled; | |
76 | + | |
77 | + qemu_fwcfg_read_entry_pio(FW_CFG_ID, 1, &dma_enabled); | |
78 | + if (dma_enabled & FW_CFG_DMA_ENABLED) | |
79 | + return true; | |
80 | + | |
81 | + return false; | |
82 | +} | |
83 | + | |
84 | +void qemu_fwcfg_read_entry(uint16_t entry, uint32_t length, void *address) | |
85 | +{ | |
86 | + if (fwcfg_dma_present) | |
87 | + qemu_fwcfg_read_entry_dma(entry, length, address); | |
88 | + else | |
89 | + qemu_fwcfg_read_entry_pio(entry, length, address); | |
90 | +} | |
91 | + | |
92 | +int qemu_fwcfg_online_cpus(void) | |
93 | +{ | |
94 | + uint16_t nb_cpus; | |
95 | + | |
96 | + if (!fwcfg_present) | |
97 | + return -ENODEV; | |
98 | + | |
99 | + qemu_fwcfg_read_entry(FW_CFG_NB_CPUS, 2, &nb_cpus); | |
100 | + | |
101 | + return le16_to_cpu(nb_cpus); | |
102 | +} | |
103 | + | |
104 | +/* | |
105 | + * This function prepares kernel for zboot. It loads kernel data | |
106 | + * to 'load_addr', initrd to 'initrd_addr' and kernel command | |
107 | + * line using qemu fw_cfg interface. | |
108 | + */ | |
109 | +static int qemu_fwcfg_setup_kernel(void *load_addr, void *initrd_addr) | |
110 | +{ | |
111 | + char *data_addr; | |
112 | + uint32_t setup_size, kernel_size, cmdline_size, initrd_size; | |
113 | + | |
114 | + qemu_fwcfg_read_entry(FW_CFG_SETUP_SIZE, 4, &setup_size); | |
115 | + qemu_fwcfg_read_entry(FW_CFG_KERNEL_SIZE, 4, &kernel_size); | |
116 | + | |
117 | + if (setup_size == 0 || kernel_size == 0) { | |
118 | + printf("warning: no kernel available\n"); | |
119 | + return -1; | |
120 | + } | |
121 | + | |
122 | + data_addr = load_addr; | |
123 | + qemu_fwcfg_read_entry(FW_CFG_SETUP_DATA, | |
124 | + le32_to_cpu(setup_size), data_addr); | |
125 | + data_addr += le32_to_cpu(setup_size); | |
126 | + | |
127 | + qemu_fwcfg_read_entry(FW_CFG_KERNEL_DATA, | |
128 | + le32_to_cpu(kernel_size), data_addr); | |
129 | + data_addr += le32_to_cpu(kernel_size); | |
130 | + | |
131 | + data_addr = initrd_addr; | |
132 | + qemu_fwcfg_read_entry(FW_CFG_INITRD_SIZE, 4, &initrd_size); | |
133 | + if (initrd_size == 0) { | |
134 | + printf("warning: no initrd available\n"); | |
135 | + } else { | |
136 | + qemu_fwcfg_read_entry(FW_CFG_INITRD_DATA, | |
137 | + le32_to_cpu(initrd_size), data_addr); | |
138 | + data_addr += le32_to_cpu(initrd_size); | |
139 | + } | |
140 | + | |
141 | + qemu_fwcfg_read_entry(FW_CFG_CMDLINE_SIZE, 4, &cmdline_size); | |
142 | + if (cmdline_size) { | |
143 | + qemu_fwcfg_read_entry(FW_CFG_CMDLINE_DATA, | |
144 | + le32_to_cpu(cmdline_size), data_addr); | |
145 | + /* | |
146 | + * if kernel cmdline only contains '\0', (e.g. no -append | |
147 | + * when invoking qemu), do not update bootargs | |
148 | + */ | |
149 | + if (*data_addr != '\0') { | |
150 | + if (setenv("bootargs", data_addr) < 0) | |
151 | + printf("warning: unable to change bootargs\n"); | |
152 | + } | |
153 | + } | |
154 | + | |
155 | + printf("loading kernel to address %p size %x", load_addr, | |
156 | + le32_to_cpu(kernel_size)); | |
157 | + if (initrd_size) | |
158 | + printf(" initrd %p size %x\n", | |
159 | + initrd_addr, | |
160 | + le32_to_cpu(initrd_size)); | |
161 | + else | |
162 | + printf("\n"); | |
163 | + | |
164 | + return 0; | |
165 | +} | |
166 | + | |
167 | +int qemu_fwcfg_read_firmware_list(void) | |
168 | +{ | |
169 | + int i; | |
170 | + uint32_t count; | |
171 | + struct fw_file *file; | |
172 | + struct list_head *entry; | |
173 | + | |
174 | + /* don't read it twice */ | |
175 | + if (!list_empty(&fw_list)) | |
176 | + return 0; | |
177 | + | |
178 | + qemu_fwcfg_read_entry(FW_CFG_FILE_DIR, 4, &count); | |
179 | + if (!count) | |
180 | + return 0; | |
181 | + | |
182 | + count = be32_to_cpu(count); | |
183 | + for (i = 0; i < count; i++) { | |
184 | + file = malloc(sizeof(*file)); | |
185 | + if (!file) { | |
186 | + printf("error: allocating resource\n"); | |
187 | + goto err; | |
188 | + } | |
189 | + qemu_fwcfg_read_entry(FW_CFG_INVALID, | |
190 | + sizeof(struct fw_cfg_file), &file->cfg); | |
191 | + file->addr = 0; | |
192 | + list_add_tail(&file->list, &fw_list); | |
193 | + } | |
194 | + | |
195 | + return 0; | |
196 | + | |
197 | +err: | |
198 | + list_for_each(entry, &fw_list) { | |
199 | + file = list_entry(entry, struct fw_file, list); | |
200 | + free(file); | |
201 | + } | |
202 | + | |
203 | + return -ENOMEM; | |
204 | +} | |
205 | + | |
206 | +struct fw_file *qemu_fwcfg_find_file(const char *name) | |
207 | +{ | |
208 | + struct list_head *entry; | |
209 | + struct fw_file *file; | |
210 | + | |
211 | + list_for_each(entry, &fw_list) { | |
212 | + file = list_entry(entry, struct fw_file, list); | |
213 | + if (!strcmp(file->cfg.name, name)) | |
214 | + return file; | |
215 | + } | |
216 | + | |
217 | + return NULL; | |
218 | +} | |
219 | + | |
220 | +void qemu_fwcfg_free_files(void) | |
221 | +{ | |
222 | + struct fw_file *file; | |
223 | + struct list_head *list; | |
224 | + | |
225 | + list_for_each(list, &fw_list) { | |
226 | + file = list_entry(list, struct fw_file, list); | |
227 | + if (file->addr) | |
228 | + free((void *)file->addr); | |
229 | + } | |
230 | +} | |
231 | + | |
232 | +static int qemu_fwcfg_list_firmware(void) | |
233 | +{ | |
234 | + int ret; | |
235 | + struct list_head *entry; | |
236 | + struct fw_file *file; | |
237 | + | |
238 | + /* make sure fw_list is loaded */ | |
239 | + ret = qemu_fwcfg_read_firmware_list(); | |
240 | + if (ret) | |
241 | + return ret; | |
242 | + | |
243 | + list_for_each(entry, &fw_list) { | |
244 | + file = list_entry(entry, struct fw_file, list); | |
245 | + printf("%-56s\n", file->cfg.name); | |
246 | + } | |
247 | + | |
248 | + return 0; | |
249 | +} | |
250 | + | |
251 | +void qemu_fwcfg_init(void) | |
252 | +{ | |
253 | + fwcfg_present = qemu_fwcfg_present(); | |
254 | + if (fwcfg_present) | |
255 | + fwcfg_dma_present = qemu_fwcfg_dma_present(); | |
256 | +} | |
257 | + | |
258 | +static int qemu_fwcfg_do_list(cmd_tbl_t *cmdtp, int flag, | |
259 | + int argc, char * const argv[]) | |
260 | +{ | |
261 | + if (qemu_fwcfg_list_firmware() < 0) | |
262 | + return CMD_RET_FAILURE; | |
263 | + | |
264 | + return 0; | |
265 | +} | |
266 | + | |
267 | +static int qemu_fwcfg_do_cpus(cmd_tbl_t *cmdtp, int flag, | |
268 | + int argc, char * const argv[]) | |
269 | +{ | |
270 | + int ret = qemu_fwcfg_online_cpus(); | |
271 | + if (ret < 0) { | |
272 | + printf("QEMU fw_cfg interface not found\n"); | |
273 | + return CMD_RET_FAILURE; | |
274 | + } | |
275 | + | |
276 | + printf("%d cpu(s) online\n", qemu_fwcfg_online_cpus()); | |
277 | + | |
278 | + return 0; | |
279 | +} | |
280 | + | |
281 | +static int qemu_fwcfg_do_load(cmd_tbl_t *cmdtp, int flag, | |
282 | + int argc, char * const argv[]) | |
283 | +{ | |
284 | + char *env; | |
285 | + void *load_addr; | |
286 | + void *initrd_addr; | |
287 | + | |
288 | + env = getenv("loadaddr"); | |
289 | + load_addr = env ? | |
290 | + (void *)simple_strtoul(env, NULL, 16) : | |
291 | + (void *)CONFIG_LOADADDR; | |
292 | + | |
293 | + env = getenv("ramdiskaddr"); | |
294 | + initrd_addr = env ? | |
295 | + (void *)simple_strtoul(env, NULL, 16) : | |
296 | + (void *)CONFIG_RAMDISK_ADDR; | |
297 | + | |
298 | + if (argc == 2) { | |
299 | + load_addr = (void *)simple_strtoul(argv[0], NULL, 16); | |
300 | + initrd_addr = (void *)simple_strtoul(argv[1], NULL, 16); | |
301 | + } else if (argc == 1) { | |
302 | + load_addr = (void *)simple_strtoul(argv[0], NULL, 16); | |
303 | + } | |
304 | + | |
305 | + return qemu_fwcfg_setup_kernel(load_addr, initrd_addr); | |
306 | +} | |
307 | + | |
308 | +static cmd_tbl_t fwcfg_commands[] = { | |
309 | + U_BOOT_CMD_MKENT(list, 0, 1, qemu_fwcfg_do_list, "", ""), | |
310 | + U_BOOT_CMD_MKENT(cpus, 0, 1, qemu_fwcfg_do_cpus, "", ""), | |
311 | + U_BOOT_CMD_MKENT(load, 2, 1, qemu_fwcfg_do_load, "", ""), | |
312 | +}; | |
313 | + | |
314 | +static int do_qemu_fw(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | |
315 | +{ | |
316 | + int ret; | |
317 | + cmd_tbl_t *fwcfg_cmd; | |
318 | + | |
319 | + if (!fwcfg_present) { | |
320 | + printf("QEMU fw_cfg interface not found\n"); | |
321 | + return CMD_RET_USAGE; | |
322 | + } | |
323 | + | |
324 | + fwcfg_cmd = find_cmd_tbl(argv[1], fwcfg_commands, | |
325 | + ARRAY_SIZE(fwcfg_commands)); | |
326 | + argc -= 2; | |
327 | + argv += 2; | |
328 | + if (!fwcfg_cmd || argc > fwcfg_cmd->maxargs) | |
329 | + return CMD_RET_USAGE; | |
330 | + | |
331 | + ret = fwcfg_cmd->cmd(fwcfg_cmd, flag, argc, argv); | |
332 | + | |
333 | + return cmd_process_error(fwcfg_cmd, ret); | |
334 | +} | |
335 | + | |
336 | +U_BOOT_CMD( | |
337 | + qfw, 4, 1, do_qemu_fw, | |
338 | + "QEMU firmware interface", | |
339 | + "<command>\n" | |
340 | + " - list : print firmware(s) currently loaded\n" | |
341 | + " - cpus : print online cpu number\n" | |
342 | + " - load <kernel addr> <initrd addr> : load kernel and initrd (if any), and setup for zboot\n" | |
343 | +) |
configs/qemu-x86_defconfig
include/qemu_fw_cfg.h
1 | +/* | |
2 | + * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com> | |
3 | + * | |
4 | + * SPDX-License-Identifier: GPL-2.0+ | |
5 | + */ | |
6 | + | |
7 | +#ifndef __FW_CFG__ | |
8 | +#define __FW_CFG__ | |
9 | + | |
10 | +#define FW_CONTROL_PORT 0x510 | |
11 | +#define FW_DATA_PORT 0x511 | |
12 | +#define FW_DMA_PORT_LOW 0x514 | |
13 | +#define FW_DMA_PORT_HIGH 0x518 | |
14 | + | |
15 | +#include <linux/list.h> | |
16 | + | |
17 | +enum qemu_fwcfg_items { | |
18 | + FW_CFG_SIGNATURE = 0x00, | |
19 | + FW_CFG_ID = 0x01, | |
20 | + FW_CFG_UUID = 0x02, | |
21 | + FW_CFG_RAM_SIZE = 0x03, | |
22 | + FW_CFG_NOGRAPHIC = 0x04, | |
23 | + FW_CFG_NB_CPUS = 0x05, | |
24 | + FW_CFG_MACHINE_ID = 0x06, | |
25 | + FW_CFG_KERNEL_ADDR = 0x07, | |
26 | + FW_CFG_KERNEL_SIZE = 0x08, | |
27 | + FW_CFG_KERNEL_CMDLINE = 0x09, | |
28 | + FW_CFG_INITRD_ADDR = 0x0a, | |
29 | + FW_CFG_INITRD_SIZE = 0x0b, | |
30 | + FW_CFG_BOOT_DEVICE = 0x0c, | |
31 | + FW_CFG_NUMA = 0x0d, | |
32 | + FW_CFG_BOOT_MENU = 0x0e, | |
33 | + FW_CFG_MAX_CPUS = 0x0f, | |
34 | + FW_CFG_KERNEL_ENTRY = 0x10, | |
35 | + FW_CFG_KERNEL_DATA = 0x11, | |
36 | + FW_CFG_INITRD_DATA = 0x12, | |
37 | + FW_CFG_CMDLINE_ADDR = 0x13, | |
38 | + FW_CFG_CMDLINE_SIZE = 0x14, | |
39 | + FW_CFG_CMDLINE_DATA = 0x15, | |
40 | + FW_CFG_SETUP_ADDR = 0x16, | |
41 | + FW_CFG_SETUP_SIZE = 0x17, | |
42 | + FW_CFG_SETUP_DATA = 0x18, | |
43 | + FW_CFG_FILE_DIR = 0x19, | |
44 | + FW_CFG_FILE_FIRST = 0x20, | |
45 | + FW_CFG_WRITE_CHANNEL = 0x4000, | |
46 | + FW_CFG_ARCH_LOCAL = 0x8000, | |
47 | + FW_CFG_INVALID = 0xffff, | |
48 | +}; | |
49 | + | |
50 | +enum { | |
51 | + BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1, | |
52 | + BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2, | |
53 | + BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3, | |
54 | +}; | |
55 | + | |
56 | +enum { | |
57 | + BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1, | |
58 | + BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2, | |
59 | +}; | |
60 | + | |
61 | +#define FW_CFG_FILE_SLOTS 0x10 | |
62 | +#define FW_CFG_MAX_ENTRY (FW_CFG_FILE_FIRST + FW_CFG_FILE_SLOTS) | |
63 | +#define FW_CFG_ENTRY_MASK ~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL) | |
64 | + | |
65 | +#define FW_CFG_MAX_FILE_PATH 56 | |
66 | +#define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH | |
67 | + | |
68 | +#define QEMU_FW_CFG_SIGNATURE (('Q' << 24) | ('E' << 16) | ('M' << 8) | 'U') | |
69 | + | |
70 | +#define FW_CFG_DMA_ERROR (1 << 0) | |
71 | +#define FW_CFG_DMA_READ (1 << 1) | |
72 | +#define FW_CFG_DMA_SKIP (1 << 2) | |
73 | +#define FW_CFG_DMA_SELECT (1 << 3) | |
74 | + | |
75 | +#define FW_CFG_DMA_ENABLED (1 << 1) | |
76 | + | |
77 | +struct fw_cfg_file { | |
78 | + __be32 size; | |
79 | + __be16 select; | |
80 | + __be16 reserved; | |
81 | + char name[FW_CFG_MAX_FILE_PATH]; | |
82 | +}; | |
83 | + | |
84 | +struct fw_file { | |
85 | + struct fw_cfg_file cfg; /* firmware file information */ | |
86 | + unsigned long addr; /* firmware file in-memory address */ | |
87 | + struct list_head list; /* list node to link to fw_list */ | |
88 | +}; | |
89 | + | |
90 | +struct fw_cfg_dma_access { | |
91 | + __be32 control; | |
92 | + __be32 length; | |
93 | + __be64 address; | |
94 | +}; | |
95 | + | |
96 | +struct bios_linker_entry { | |
97 | + __le32 command; | |
98 | + union { | |
99 | + /* | |
100 | + * COMMAND_ALLOCATE - allocate a table from @alloc.file | |
101 | + * subject to @alloc.align alignment (must be power of 2) | |
102 | + * and @alloc.zone (can be HIGH or FSEG) requirements. | |
103 | + * | |
104 | + * Must appear exactly once for each file, and before | |
105 | + * this file is referenced by any other command. | |
106 | + */ | |
107 | + struct { | |
108 | + char file[BIOS_LINKER_LOADER_FILESZ]; | |
109 | + __le32 align; | |
110 | + uint8_t zone; | |
111 | + } alloc; | |
112 | + | |
113 | + /* | |
114 | + * COMMAND_ADD_POINTER - patch the table (originating from | |
115 | + * @dest_file) at @pointer.offset, by adding a pointer to the | |
116 | + * table originating from @src_file. 1,2,4 or 8 byte unsigned | |
117 | + * addition is used depending on @pointer.size. | |
118 | + */ | |
119 | + struct { | |
120 | + char dest_file[BIOS_LINKER_LOADER_FILESZ]; | |
121 | + char src_file[BIOS_LINKER_LOADER_FILESZ]; | |
122 | + __le32 offset; | |
123 | + uint8_t size; | |
124 | + } pointer; | |
125 | + | |
126 | + /* | |
127 | + * COMMAND_ADD_CHECKSUM - calculate checksum of the range | |
128 | + * specified by @cksum_start and @cksum_length fields, | |
129 | + * and then add the value at @cksum.offset. | |
130 | + * Checksum simply sums -X for each byte X in the range | |
131 | + * using 8-bit math. | |
132 | + */ | |
133 | + struct { | |
134 | + char file[BIOS_LINKER_LOADER_FILESZ]; | |
135 | + __le32 offset; | |
136 | + __le32 start; | |
137 | + __le32 length; | |
138 | + } cksum; | |
139 | + | |
140 | + /* padding */ | |
141 | + char pad[124]; | |
142 | + }; | |
143 | +} __packed; | |
144 | + | |
145 | +/** | |
146 | + * Initialize QEMU fw_cfg interface | |
147 | + */ | |
148 | +void qemu_fwcfg_init(void); | |
149 | + | |
150 | +void qemu_fwcfg_read_entry(uint16_t entry, uint32_t length, void *address); | |
151 | +int qemu_fwcfg_read_firmware_list(void); | |
152 | +struct fw_file *qemu_fwcfg_find_file(const char *name); | |
153 | +void qemu_fwcfg_free_files(void); | |
154 | + | |
155 | +/** | |
156 | + * Get system cpu number | |
157 | + * | |
158 | + * @return: cpu number in system | |
159 | + */ | |
160 | +int qemu_fwcfg_online_cpus(void); | |
161 | + | |
162 | +#endif |