Commit | Line | Data |
---|---|---|
f60df20a MY |
1 | /* |
2 | * (C) Copyright 2015 Miao Yan <yanmiaoebst@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 | ||
14 | static bool fwcfg_present; | |
15 | static bool fwcfg_dma_present; | |
16 | ||
17 | /* Read configuration item using fw_cfg PIO interface */ | |
18 | static void qemu_fwcfg_read_entry_pio(uint16_t entry, | |
19 | uint32_t size, void *address) | |
20 | { | |
21 | uint32_t i = 0; | |
22 | uint8_t *data = address; | |
23 | ||
24 | /* | |
25 | * writting FW_CFG_INVALID will cause read operation to resume at | |
26 | * last offset, otherwise read will start at offset 0 | |
27 | */ | |
28 | if (entry != FW_CFG_INVALID) | |
29 | outw(entry, FW_CONTROL_PORT); | |
30 | while (size--) | |
31 | data[i++] = inb(FW_DATA_PORT); | |
32 | } | |
33 | ||
34 | /* Read configuration item using fw_cfg DMA interface */ | |
35 | static void qemu_fwcfg_read_entry_dma(uint16_t entry, | |
36 | uint32_t size, void *address) | |
37 | { | |
38 | struct fw_cfg_dma_access dma; | |
39 | ||
40 | dma.length = cpu_to_be32(size); | |
41 | dma.address = cpu_to_be64((uintptr_t)address); | |
42 | dma.control = cpu_to_be32(FW_CFG_DMA_READ); | |
43 | ||
44 | /* | |
45 | * writting FW_CFG_INVALID will cause read operation to resume at | |
46 | * last offset, otherwise read will start at offset 0 | |
47 | */ | |
48 | if (entry != FW_CFG_INVALID) | |
49 | dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16)); | |
50 | ||
51 | barrier(); | |
52 | ||
53 | debug("qemu_fwcfg_dma_read_entry: addr %p, length %u control 0x%x\n", | |
54 | address, size, be32_to_cpu(dma.control)); | |
55 | ||
56 | outl(cpu_to_be32((uint32_t)&dma), FW_DMA_PORT_HIGH); | |
57 | ||
58 | while (be32_to_cpu(dma.control) & ~FW_CFG_DMA_ERROR) | |
59 | __asm__ __volatile__ ("pause"); | |
60 | } | |
61 | ||
62 | static bool qemu_fwcfg_present(void) | |
63 | { | |
64 | uint32_t qemu; | |
65 | ||
66 | qemu_fwcfg_read_entry_pio(FW_CFG_SIGNATURE, 4, &qemu); | |
67 | return be32_to_cpu(qemu) == QEMU_FW_CFG_SIGNATURE; | |
68 | } | |
69 | ||
70 | static bool qemu_fwcfg_dma_present(void) | |
71 | { | |
72 | uint8_t dma_enabled; | |
73 | ||
74 | qemu_fwcfg_read_entry_pio(FW_CFG_ID, 1, &dma_enabled); | |
75 | if (dma_enabled & FW_CFG_DMA_ENABLED) | |
76 | return true; | |
77 | ||
78 | return false; | |
79 | } | |
80 | ||
81 | static void qemu_fwcfg_read_entry(uint16_t entry, | |
82 | uint32_t length, void *address) | |
83 | { | |
84 | if (fwcfg_dma_present) | |
85 | qemu_fwcfg_read_entry_dma(entry, length, address); | |
86 | else | |
87 | qemu_fwcfg_read_entry_pio(entry, length, address); | |
88 | } | |
89 | ||
90 | int qemu_fwcfg_online_cpus(void) | |
91 | { | |
92 | uint16_t nb_cpus; | |
93 | ||
94 | if (!fwcfg_present) | |
95 | return -ENODEV; | |
96 | ||
97 | qemu_fwcfg_read_entry(FW_CFG_NB_CPUS, 2, &nb_cpus); | |
98 | ||
99 | return le16_to_cpu(nb_cpus); | |
100 | } | |
101 | ||
102 | /* | |
103 | * This function prepares kernel for zboot. It loads kernel data | |
104 | * to 'load_addr', initrd to 'initrd_addr' and kernel command | |
105 | * line using qemu fw_cfg interface. | |
106 | */ | |
107 | static int qemu_fwcfg_setup_kernel(void *load_addr, void *initrd_addr) | |
108 | { | |
109 | char *data_addr; | |
110 | uint32_t setup_size, kernel_size, cmdline_size, initrd_size; | |
111 | ||
112 | qemu_fwcfg_read_entry(FW_CFG_SETUP_SIZE, 4, &setup_size); | |
113 | qemu_fwcfg_read_entry(FW_CFG_KERNEL_SIZE, 4, &kernel_size); | |
114 | ||
115 | if (setup_size == 0 || kernel_size == 0) { | |
116 | printf("warning: no kernel available\n"); | |
117 | return -1; | |
118 | } | |
119 | ||
120 | data_addr = load_addr; | |
121 | qemu_fwcfg_read_entry(FW_CFG_SETUP_DATA, | |
122 | le32_to_cpu(setup_size), data_addr); | |
123 | data_addr += le32_to_cpu(setup_size); | |
124 | ||
125 | qemu_fwcfg_read_entry(FW_CFG_KERNEL_DATA, | |
126 | le32_to_cpu(kernel_size), data_addr); | |
127 | data_addr += le32_to_cpu(kernel_size); | |
128 | ||
129 | data_addr = initrd_addr; | |
130 | qemu_fwcfg_read_entry(FW_CFG_INITRD_SIZE, 4, &initrd_size); | |
131 | if (initrd_size == 0) { | |
132 | printf("warning: no initrd available\n"); | |
133 | } else { | |
134 | qemu_fwcfg_read_entry(FW_CFG_INITRD_DATA, | |
135 | le32_to_cpu(initrd_size), data_addr); | |
136 | data_addr += le32_to_cpu(initrd_size); | |
137 | } | |
138 | ||
139 | qemu_fwcfg_read_entry(FW_CFG_CMDLINE_SIZE, 4, &cmdline_size); | |
140 | if (cmdline_size) { | |
141 | qemu_fwcfg_read_entry(FW_CFG_CMDLINE_DATA, | |
142 | le32_to_cpu(cmdline_size), data_addr); | |
143 | /* | |
144 | * if kernel cmdline only contains '\0', (e.g. no -append | |
145 | * when invoking qemu), do not update bootargs | |
146 | */ | |
147 | if (*data_addr != '\0') { | |
148 | if (setenv("bootargs", data_addr) < 0) | |
149 | printf("warning: unable to change bootargs\n"); | |
150 | } | |
151 | } | |
152 | ||
153 | printf("loading kernel to address %p size %x", load_addr, | |
154 | le32_to_cpu(kernel_size)); | |
155 | if (initrd_size) | |
156 | printf(" initrd %p size %x\n", | |
157 | initrd_addr, | |
158 | le32_to_cpu(initrd_size)); | |
159 | else | |
160 | printf("\n"); | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | static int qemu_fwcfg_list_firmware(void) | |
166 | { | |
167 | int i; | |
168 | uint32_t count; | |
169 | struct fw_cfg_files *files; | |
170 | ||
171 | qemu_fwcfg_read_entry(FW_CFG_FILE_DIR, 4, &count); | |
172 | if (!count) | |
173 | return 0; | |
174 | ||
175 | count = be32_to_cpu(count); | |
176 | files = malloc(count * sizeof(struct fw_cfg_file)); | |
177 | if (!files) | |
178 | return -ENOMEM; | |
179 | ||
180 | files->count = count; | |
181 | qemu_fwcfg_read_entry(FW_CFG_INVALID, | |
182 | count * sizeof(struct fw_cfg_file), | |
183 | files->files); | |
184 | ||
185 | for (i = 0; i < files->count; i++) | |
186 | printf("%-56s\n", files->files[i].name); | |
187 | free(files); | |
188 | return 0; | |
189 | } | |
190 | ||
191 | void qemu_fwcfg_init(void) | |
192 | { | |
193 | fwcfg_present = qemu_fwcfg_present(); | |
194 | if (fwcfg_present) | |
195 | fwcfg_dma_present = qemu_fwcfg_dma_present(); | |
196 | } | |
197 | ||
198 | static int qemu_fwcfg_do_list(cmd_tbl_t *cmdtp, int flag, | |
199 | int argc, char * const argv[]) | |
200 | { | |
201 | if (qemu_fwcfg_list_firmware() < 0) | |
202 | return CMD_RET_FAILURE; | |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
207 | static int qemu_fwcfg_do_cpus(cmd_tbl_t *cmdtp, int flag, | |
208 | int argc, char * const argv[]) | |
209 | { | |
210 | int ret = qemu_fwcfg_online_cpus(); | |
211 | if (ret < 0) { | |
212 | printf("QEMU fw_cfg interface not found\n"); | |
213 | return CMD_RET_FAILURE; | |
214 | } | |
215 | ||
216 | printf("%d cpu(s) online\n", qemu_fwcfg_online_cpus()); | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | static int qemu_fwcfg_do_load(cmd_tbl_t *cmdtp, int flag, | |
222 | int argc, char * const argv[]) | |
223 | { | |
224 | char *env; | |
225 | void *load_addr; | |
226 | void *initrd_addr; | |
227 | ||
228 | env = getenv("loadaddr"); | |
229 | load_addr = env ? | |
230 | (void *)simple_strtoul(env, NULL, 16) : | |
231 | (void *)CONFIG_LOADADDR; | |
232 | ||
233 | env = getenv("ramdiskaddr"); | |
234 | initrd_addr = env ? | |
235 | (void *)simple_strtoul(env, NULL, 16) : | |
236 | (void *)CONFIG_RAMDISK_ADDR; | |
237 | ||
238 | if (argc == 2) { | |
239 | load_addr = (void *)simple_strtoul(argv[0], NULL, 16); | |
240 | initrd_addr = (void *)simple_strtoul(argv[1], NULL, 16); | |
241 | } else if (argc == 1) { | |
242 | load_addr = (void *)simple_strtoul(argv[0], NULL, 16); | |
243 | } | |
244 | ||
245 | return qemu_fwcfg_setup_kernel(load_addr, initrd_addr); | |
246 | } | |
247 | ||
248 | static cmd_tbl_t fwcfg_commands[] = { | |
249 | U_BOOT_CMD_MKENT(list, 0, 1, qemu_fwcfg_do_list, "", ""), | |
250 | U_BOOT_CMD_MKENT(cpus, 0, 1, qemu_fwcfg_do_cpus, "", ""), | |
251 | U_BOOT_CMD_MKENT(load, 2, 1, qemu_fwcfg_do_load, "", ""), | |
252 | }; | |
253 | ||
254 | static int do_qemu_fw(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | |
255 | { | |
256 | int ret; | |
257 | cmd_tbl_t *fwcfg_cmd; | |
258 | ||
259 | if (!fwcfg_present) { | |
260 | printf("QEMU fw_cfg interface not found\n"); | |
261 | return CMD_RET_USAGE; | |
262 | } | |
263 | ||
264 | fwcfg_cmd = find_cmd_tbl(argv[1], fwcfg_commands, | |
265 | ARRAY_SIZE(fwcfg_commands)); | |
266 | argc -= 2; | |
267 | argv += 2; | |
268 | if (!fwcfg_cmd || argc > fwcfg_cmd->maxargs) | |
269 | return CMD_RET_USAGE; | |
270 | ||
271 | ret = fwcfg_cmd->cmd(fwcfg_cmd, flag, argc, argv); | |
272 | ||
273 | return cmd_process_error(fwcfg_cmd, ret); | |
274 | } | |
275 | ||
276 | U_BOOT_CMD( | |
277 | qfw, 4, 1, do_qemu_fw, | |
278 | "QEMU firmware interface", | |
279 | "<command>\n" | |
280 | " - list : print firmware(s) currently loaded\n" | |
281 | " - cpus : print online cpu number\n" | |
282 | " - load <kernel addr> <initrd addr> : load kernel and initrd (if any), and setup for zboot\n" | |
283 | ) |