blob: 0d0cf7646890b199655c85584285f2ace3ee02ec [file] [log] [blame]
Heinrich Schuchardtd2cd3d62023-12-19 16:04:01 +01001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com>
4 * (C) Copyright 2021 Asherah Connor <ashe@kivikakk.ee>
5 */
6
7#define LOG_CATEGORY UCLASS_QFW
8
9#include <acpi/acpi_table.h>
Simon Glass4d8c5202025-01-10 17:00:17 -070010#include <bloblist.h>
Heinrich Schuchardtd2cd3d62023-12-19 16:04:01 +010011#include <errno.h>
12#include <malloc.h>
Heinrich Schuchardt7028f9f2023-12-19 16:04:04 +010013#include <mapmem.h>
Heinrich Schuchardtd2cd3d62023-12-19 16:04:01 +010014#include <qfw.h>
15#include <tables_csum.h>
16#include <stdio.h>
Heinrich Schuchardt7028f9f2023-12-19 16:04:04 +010017#include <linux/sizes.h>
Heinrich Schuchardtd2cd3d62023-12-19 16:04:01 +010018#include <asm/byteorder.h>
19#include <asm/global_data.h>
20
21DECLARE_GLOBAL_DATA_PTR;
22
23/*
24 * This function allocates memory for ACPI tables
25 *
26 * @entry : BIOS linker command entry which tells where to allocate memory
27 * (either high memory or low memory)
28 * @addr : The address that should be used for low memory allcation. If the
29 * memory allocation request is 'ZONE_HIGH' then this parameter will
30 * be ignored.
31 * @return: 0 on success, or negative value on failure
32 */
33static int bios_linker_allocate(struct udevice *dev,
34 struct bios_linker_entry *entry, ulong *addr)
35{
36 uint32_t size, align;
37 struct fw_file *file;
38 unsigned long aligned_addr;
39
40 align = le32_to_cpu(entry->alloc.align);
41 /* align must be power of 2 */
42 if (align & (align - 1)) {
43 printf("error: wrong alignment %u\n", align);
44 return -EINVAL;
45 }
46
47 file = qfw_find_file(dev, entry->alloc.file);
48 if (!file) {
49 printf("error: can't find file %s\n", entry->alloc.file);
50 return -ENOENT;
51 }
52
53 size = be32_to_cpu(file->cfg.size);
54
55 /*
56 * ZONE_HIGH means we need to allocate from high memory, since
57 * malloc space is already at the end of RAM, so we directly use it.
58 * If allocation zone is ZONE_FSEG, then we use the 'addr' passed
59 * in which is low memory
60 */
61 if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH) {
62 aligned_addr = (unsigned long)memalign(align, size);
63 if (!aligned_addr) {
64 printf("error: allocating resource\n");
65 return -ENOMEM;
66 }
67 if (aligned_addr < gd->arch.table_start_high)
68 gd->arch.table_start_high = aligned_addr;
69 if (aligned_addr + size > gd->arch.table_end_high)
70 gd->arch.table_end_high = aligned_addr + size;
71
72 } else if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG) {
73 aligned_addr = ALIGN(*addr, align);
74 } else {
75 printf("error: invalid allocation zone\n");
76 return -EINVAL;
77 }
78
79 debug("bios_linker_allocate: allocate file %s, size %u, zone %d, align %u, addr 0x%lx\n",
80 file->cfg.name, size, entry->alloc.zone, align, aligned_addr);
81
82 qfw_read_entry(dev, be16_to_cpu(file->cfg.select), size,
83 (void *)aligned_addr);
84 file->addr = aligned_addr;
85
86 /* adjust address for low memory allocation */
87 if (entry->alloc.zone == BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG)
88 *addr = (aligned_addr + size);
89
90 return 0;
91}
92
93/*
94 * This function patches ACPI tables previously loaded
95 * by bios_linker_allocate()
96 *
97 * @entry : BIOS linker command entry which tells how to patch
98 * ACPI tables
99 * @return: 0 on success, or negative value on failure
100 */
101static int bios_linker_add_pointer(struct udevice *dev,
102 struct bios_linker_entry *entry)
103{
104 struct fw_file *dest, *src;
105 uint32_t offset = le32_to_cpu(entry->pointer.offset);
106 uint64_t pointer = 0;
107
108 dest = qfw_find_file(dev, entry->pointer.dest_file);
109 if (!dest || !dest->addr)
110 return -ENOENT;
111 src = qfw_find_file(dev, entry->pointer.src_file);
112 if (!src || !src->addr)
113 return -ENOENT;
114
115 debug("bios_linker_add_pointer: dest->addr 0x%lx, src->addr 0x%lx, offset 0x%x size %u, 0x%llx\n",
116 dest->addr, src->addr, offset, entry->pointer.size, pointer);
117
118 memcpy(&pointer, (char *)dest->addr + offset, entry->pointer.size);
119 pointer = le64_to_cpu(pointer);
120 pointer += (unsigned long)src->addr;
121 pointer = cpu_to_le64(pointer);
122 memcpy((char *)dest->addr + offset, &pointer, entry->pointer.size);
123
124 return 0;
125}
126
127/*
128 * This function updates checksum fields of ACPI tables previously loaded
129 * by bios_linker_allocate()
130 *
131 * @entry : BIOS linker command entry which tells where to update ACPI table
132 * checksums
133 * @return: 0 on success, or negative value on failure
134 */
135static int bios_linker_add_checksum(struct udevice *dev,
136 struct bios_linker_entry *entry)
137{
138 struct fw_file *file;
139 uint8_t *data, cksum = 0;
140 uint8_t *cksum_start;
141
142 file = qfw_find_file(dev, entry->cksum.file);
143 if (!file || !file->addr)
144 return -ENOENT;
145
146 data = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.offset));
147 cksum_start = (uint8_t *)(file->addr + le32_to_cpu(entry->cksum.start));
148 cksum = table_compute_checksum(cksum_start,
149 le32_to_cpu(entry->cksum.length));
150 *data = cksum;
151
152 return 0;
153}
154
155/* This function loads and patches ACPI tables provided by QEMU */
156ulong write_acpi_tables(ulong addr)
157{
158 int i, ret;
159 struct fw_file *file;
160 struct bios_linker_entry *table_loader;
161 struct bios_linker_entry *entry;
162 uint32_t size;
163 struct udevice *dev;
Simon Glass4d8c5202025-01-10 17:00:17 -0700164 struct acpi_ctx *ctx;
165
166 ctx = malloc(sizeof(*ctx));
167 if (!ctx) {
168 printf("error: out of memory for acpi ctx\n");
169 return addr;
170 }
171
172 acpi_setup_ctx(ctx, addr);
Heinrich Schuchardtd2cd3d62023-12-19 16:04:01 +0100173
174 ret = qfw_get_dev(&dev);
175 if (ret) {
176 printf("error: no qfw\n");
177 return addr;
178 }
179
180 /* make sure fw_list is loaded */
181 ret = qfw_read_firmware_list(dev);
182 if (ret) {
183 printf("error: can't read firmware file list\n");
184 return addr;
185 }
186
187 file = qfw_find_file(dev, "etc/table-loader");
188 if (!file) {
189 printf("error: can't find etc/table-loader\n");
190 return addr;
191 }
192
193 size = be32_to_cpu(file->cfg.size);
194 if ((size % sizeof(*entry)) != 0) {
195 printf("error: table-loader maybe corrupted\n");
196 return addr;
197 }
198
199 table_loader = malloc(size);
200 if (!table_loader) {
201 printf("error: no memory for table-loader\n");
202 return addr;
203 }
204
205 /* QFW always puts tables at high addresses */
206 gd->arch.table_start_high = (ulong)table_loader;
207 gd->arch.table_end_high = (ulong)table_loader;
208
209 qfw_read_entry(dev, be16_to_cpu(file->cfg.select), size, table_loader);
210
211 for (i = 0; i < (size / sizeof(*entry)); i++) {
212 entry = table_loader + i;
213 switch (le32_to_cpu(entry->command)) {
214 case BIOS_LINKER_LOADER_COMMAND_ALLOCATE:
215 ret = bios_linker_allocate(dev, entry, &addr);
216 if (ret)
217 goto out;
218 break;
219 case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER:
220 ret = bios_linker_add_pointer(dev, entry);
221 if (ret)
222 goto out;
223 break;
224 case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM:
225 ret = bios_linker_add_checksum(dev, entry);
226 if (ret)
227 goto out;
228 break;
229 default:
230 break;
231 }
232 }
233
234out:
235 if (ret) {
236 struct fw_cfg_file_iter iter;
237 for (file = qfw_file_iter_init(dev, &iter);
238 !qfw_file_iter_end(&iter);
239 file = qfw_file_iter_next(&iter)) {
240 if (file->addr) {
241 free((void *)file->addr);
242 file->addr = 0;
243 }
244 }
245 }
246
247 free(table_loader);
248
249 gd_set_acpi_start(acpi_get_rsdp_addr());
250
251 return addr;
252}
253
254ulong acpi_get_rsdp_addr(void)
255{
256 int ret;
257 struct fw_file *file;
258 struct udevice *dev;
259
260 ret = qfw_get_dev(&dev);
261 if (ret) {
262 printf("error: no qfw\n");
263 return 0;
264 }
265
266 file = qfw_find_file(dev, "etc/acpi/rsdp");
267 return file->addr;
268}
Heinrich Schuchardt7028f9f2023-12-19 16:04:04 +0100269
Simon Glass4d8c5202025-01-10 17:00:17 -0700270void acpi_write_rsdp(struct acpi_rsdp *rsdp, struct acpi_rsdt *rsdt,
271 struct acpi_xsdt *xsdt)
272{
273 memset(rsdp, 0, sizeof(struct acpi_rsdp));
274
275 memcpy(rsdp->signature, RSDP_SIG, 8);
276 memcpy(rsdp->oem_id, OEM_ID, 6);
277
278 if (rsdt)
279 rsdp->rsdt_address = nomap_to_sysmem(rsdt);
280
281 if (xsdt)
282 rsdp->xsdt_address = nomap_to_sysmem(xsdt);
283
284 rsdp->length = sizeof(struct acpi_rsdp);
285 rsdp->revision = ACPI_RSDP_REV_ACPI_2_0;
286
287 /* Calculate checksums */
288 rsdp->checksum = table_compute_checksum(rsdp, 20);
289 rsdp->ext_checksum = table_compute_checksum(rsdp,
290 sizeof(struct acpi_rsdp));
291}
292
Heinrich Schuchardt7028f9f2023-12-19 16:04:04 +0100293#ifndef CONFIG_X86
294static int evt_write_acpi_tables(void)
295{
296 ulong addr, end;
297 void *ptr;
298
299 /* Reserve 64K for ACPI tables, aligned to a 4K boundary */
Simon Glass4d8c5202025-01-10 17:00:17 -0700300 ptr = bloblist_add(BLOBLISTT_ACPI_TABLES, SZ_64K, 12);
Heinrich Schuchardt7028f9f2023-12-19 16:04:04 +0100301 if (!ptr)
Simon Glass4d8c5202025-01-10 17:00:17 -0700302 return -ENOBUFS;
Heinrich Schuchardt7028f9f2023-12-19 16:04:04 +0100303 addr = map_to_sysmem(ptr);
304
305 /* Generate ACPI tables */
306 end = write_acpi_tables(addr);
307 gd->arch.table_start = addr;
308 gd->arch.table_end = addr;
309
310 return 0;
311}
312
313EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, evt_write_acpi_tables);
314#endif