blob: 7d637c15ba056832d9647fa4712c57392cb42a40 [file] [log] [blame]
Simon Glassb9474a82024-08-07 16:47:28 -06001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * UPL handoff generation
4 *
5 * Copyright 2024 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
9#define LOG_CATEGORY UCLASS_BOOTSTD
10
11#include <log.h>
12#include <upl.h>
13#include <dm/ofnode.h>
14#include "upl_common.h"
15
16/**
17 * write_addr() - Write an address
18 *
19 * Writes an address in the correct format, either 32- or 64-bit
20 *
21 * @upl: UPL state
22 * @node: Node to write to
23 * @prop: Property name to write
24 * @addr: Address to write
25 * Return: 0 if OK, -ve on error
26 */
27static int write_addr(const struct upl *upl, ofnode node, const char *prop,
28 ulong addr)
29{
30 int ret;
31
32 if (upl->addr_cells == 1)
33 ret = ofnode_write_u32(node, prop, addr);
34 else
35 ret = ofnode_write_u64(node, prop, addr);
36
37 return ret;
38}
39
40/**
41 * write_size() - Write a size
42 *
43 * Writes a size in the correct format, either 32- or 64-bit
44 *
45 * @upl: UPL state
46 * @node: Node to write to
47 * @prop: Property name to write
48 * @size: Size to write
49 * Return: 0 if OK, -ve on error
50 */
51static int write_size(const struct upl *upl, ofnode node, const char *prop,
52 ulong size)
53{
54 int ret;
55
56 if (upl->size_cells == 1)
57 ret = ofnode_write_u32(node, prop, size);
58 else
59 ret = ofnode_write_u64(node, prop, size);
60
61 return ret;
62}
63
64/**
65 * ofnode_write_bitmask() - Write a bit mask as a string list
66 *
67 * @node: Node to write to
68 * @prop: Property name to write
69 * @names: Array of names for each bit
70 * @count: Number of array entries
71 * @value: Bit-mask value to write
72 * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the
73 * string is too long for the (internal) buffer
74 */
75static int ofnode_write_bitmask(ofnode node, const char *prop,
76 const char *const names[], uint count,
77 uint value)
78{
79 char buf[128];
80 char *ptr, *end = buf + sizeof(buf);
81 uint bit;
82 int ret;
83
84 ptr = buf;
85 for (bit = 0; bit < count; bit++) {
86 if (value & BIT(bit)) {
87 const char *str = names[bit];
88 uint len;
89
90 if (!str) {
91 log_debug("Unnamed bit number %d\n", bit);
92 return log_msg_ret("bit", -EINVAL);
93 }
94 len = strlen(str) + 1;
95 if (ptr + len > end) {
96 log_debug("String array too long\n");
97 return log_msg_ret("bit", -ENOSPC);
98 }
99
100 memcpy(ptr, str, len);
101 ptr += len;
102 }
103 }
104
105 ret = ofnode_write_prop(node, prop, buf, ptr - buf, true);
106 if (ret)
107 return log_msg_ret("wri", ret);
108
109 return 0;
110}
111
112/**
113 * ofnode_write_value() - Write an int as a string value using a lookup
114 *
115 * @node: Node to write to
116 * @prop: Property name to write
117 * @names: Array of names for each int value
118 * @count: Number of array entries
119 * @value: Int value to write
120 * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the
121 * string is too long for the (internal) buffer
122 */
123static int ofnode_write_value(ofnode node, const char *prop,
124 const char *const names[], uint count,
125 uint value)
126{
127 const char *str;
128 int ret;
129
130 if (value >= count) {
131 log_debug("Value of range %d\n", value);
132 return log_msg_ret("val", -ERANGE);
133 }
134 str = names[value];
135 if (!str) {
136 log_debug("Unnamed value %d\n", value);
137 return log_msg_ret("val", -EINVAL);
138 }
139 ret = ofnode_write_string(node, prop, str);
140 if (ret)
141 return log_msg_ret("wri", ret);
142
143 return 0;
144}
145
146/**
147 * add_root_props() - Add root properties to the tree
148 *
149 * @node: Node to add to
150 * Return 0 if OK, -ve on error
151 */
152static int add_root_props(const struct upl *upl, ofnode node)
153{
154 int ret;
155
156 ret = ofnode_write_u32(node, UPLP_ADDRESS_CELLS, upl->addr_cells);
157 if (!ret)
158 ret = ofnode_write_u32(node, UPLP_SIZE_CELLS, upl->size_cells);
159 if (ret)
160 return log_msg_ret("cel", ret);
161
162 return 0;
163}
164
165/**
166 * add_upl_params() - Add UPL parameters node
167 *
168 * @upl: UPL state
169 * @options: /options node to add to
170 * Return 0 if OK, -ve on error
171 */
172static int add_upl_params(const struct upl *upl, ofnode options)
173{
174 ofnode node;
175 int ret;
176
177 ret = ofnode_add_subnode(options, UPLN_UPL_PARAMS, &node);
178 if (ret)
179 return log_msg_ret("img", ret);
180
181 ret = write_addr(upl, node, UPLP_SMBIOS, upl->smbios);
182 if (!ret)
183 ret = write_addr(upl, node, UPLP_ACPI, upl->acpi);
184 if (!ret && upl->bootmode)
185 ret = ofnode_write_bitmask(node, UPLP_BOOTMODE, bootmode_names,
186 UPLBM_COUNT, upl->bootmode);
187 if (!ret)
188 ret = ofnode_write_u32(node, UPLP_ADDR_WIDTH, upl->addr_width);
189 if (!ret)
190 ret = ofnode_write_u32(node, UPLP_ACPI_NVS_SIZE,
191 upl->acpi_nvs_size);
192 if (ret)
193 return log_msg_ret("cnf", ret);
194
195 return 0;
196}
197
198/**
199 * add_upl_image() - Add /options/upl-image nodes and properties to the tree
200 *
201 * @upl: UPL state
202 * @node: /options node to add to
203 * Return 0 if OK, -ve on error
204 */
205static int add_upl_image(const struct upl *upl, ofnode options)
206{
207 ofnode node;
208 int ret, i;
209
210 ret = ofnode_add_subnode(options, UPLN_UPL_IMAGE, &node);
211 if (ret)
212 return log_msg_ret("img", ret);
213
214 if (upl->fit)
215 ret = ofnode_write_u32(node, UPLP_FIT, upl->fit);
216 if (!ret && upl->conf_offset)
217 ret = ofnode_write_u32(node, UPLP_CONF_OFFSET,
218 upl->conf_offset);
219 if (ret)
220 return log_msg_ret("cnf", ret);
221
222 for (i = 0; i < upl->image.count; i++) {
223 const struct upl_image *img = alist_get(&upl->image, i,
224 struct upl_image);
225 ofnode subnode;
226 char name[10];
227
228 snprintf(name, sizeof(name), UPLN_IMAGE "-%d", i + 1);
229 ret = ofnode_add_subnode(node, name, &subnode);
230 if (ret)
231 return log_msg_ret("sub", ret);
232
233 ret = write_addr(upl, subnode, UPLP_LOAD, img->load);
234 if (!ret)
235 ret = write_size(upl, subnode, UPLP_SIZE, img->size);
236 if (!ret && img->offset)
237 ret = ofnode_write_u32(subnode, UPLP_OFFSET,
238 img->offset);
239 ret = ofnode_write_string(subnode, UPLP_DESCRIPTION,
240 img->description);
241 if (ret)
242 return log_msg_ret("sim", ret);
243 }
244
245 return 0;
246}
247
248/**
249 * buffer_addr_size() - Generate a set of addr/size pairs
250 *
251 * Each base/size value from each region is written to the buffer in a suitable
252 * format to be written to the devicetree
253 *
254 * @upl: UPL state
255 * @buf: Buffer to write to
256 * @size: Buffer size
257 * @num_regions: Number of regions to process
258 * @region: List of regions to process (struct memregion)
259 * Returns: Number of bytes written, or -ENOSPC if the buffer is too small
260 */
261static int buffer_addr_size(const struct upl *upl, char *buf, int size,
262 uint num_regions, const struct alist *region)
263{
264 char *ptr, *end = buf + size;
265 int i;
266
267 ptr = buf;
268 for (i = 0; i < num_regions; i++) {
269 const struct memregion *reg = alist_get(region, i,
270 struct memregion);
271
272 if (upl->addr_cells == 1)
273 *(u32 *)ptr = cpu_to_fdt32(reg->base);
274 else
275 *(u64 *)ptr = cpu_to_fdt64(reg->base);
276 ptr += upl->addr_cells * sizeof(u32);
277
278 if (upl->size_cells == 1)
279 *(u32 *)ptr = cpu_to_fdt32(reg->size);
280 else
281 *(u64 *)ptr = cpu_to_fdt64(reg->size);
282 ptr += upl->size_cells * sizeof(u32);
283 if (ptr > end)
284 return -ENOSPC;
285 }
286
287 return ptr - buf;
288}
289
290/**
291 * add_upl_memory() - Add /memory nodes to the tree
292 *
293 * @upl: UPL state
294 * @root: Parent node to contain the new /memory nodes
295 * Return 0 if OK, -ve on error
296 */
297static int add_upl_memory(const struct upl *upl, ofnode root)
298{
299 int i;
300
301 for (i = 0; i < upl->mem.count; i++) {
302 const struct upl_mem *mem = alist_get(&upl->mem, i,
303 struct upl_mem);
304 char buf[mem->region.count * sizeof(64) * 2];
305 const struct memregion *first;
306 char name[26];
307 int ret, len;
308 ofnode node;
309
310 if (!mem->region.count) {
311 log_debug("Memory %d has no regions\n", i);
312 return log_msg_ret("reg", -EINVAL);
313 }
314 first = alist_get(&mem->region, 0, struct memregion);
315 sprintf(name, UPLN_MEMORY "@0x%lx", first->base);
316 ret = ofnode_add_subnode(root, name, &node);
317 if (ret)
318 return log_msg_ret("mem", ret);
319
320 len = buffer_addr_size(upl, buf, sizeof(buf), mem->region.count,
321 &mem->region);
322 if (len < 0)
323 return log_msg_ret("buf", len);
324
325 ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
326 if (!ret && mem->hotpluggable)
327 ret = ofnode_write_bool(node, UPLP_HOTPLUGGABLE,
328 mem->hotpluggable);
329 if (ret)
330 return log_msg_ret("lst", ret);
331 }
332
333 return 0;
334}
335
336/**
337 * add_upl_memmap() - Add memory-map nodes to the tree
338 *
339 * @upl: UPL state
340 * @root: Parent node to contain the new /memory-map node and its subnodes
341 * Return 0 if OK, -ve on error
342 */
343static int add_upl_memmap(const struct upl *upl, ofnode root)
344{
345 ofnode mem_node;
346 int i, ret;
347
348 if (!upl->memmap.count)
349 return 0;
350 ret = ofnode_add_subnode(root, UPLN_MEMORY_MAP, &mem_node);
351 if (ret)
352 return log_msg_ret("img", ret);
353
354 for (i = 0; i < upl->memmap.count; i++) {
355 const struct upl_memmap *memmap = alist_get(&upl->memmap, i,
356 struct upl_memmap);
357 char buf[memmap->region.count * sizeof(64) * 2];
358 const struct memregion *first;
359 char name[26];
360 int ret, len;
361 ofnode node;
362
363 if (!memmap->region.count) {
364 log_debug("Memory %d has no regions\n", i);
365 return log_msg_ret("reg", -EINVAL);
366 }
367 first = alist_get(&memmap->region, 0, struct memregion);
368 sprintf(name, "%s@0x%lx", memmap->name, first->base);
369 ret = ofnode_add_subnode(mem_node, name, &node);
370 if (ret)
371 return log_msg_ret("memmap", ret);
372
373 len = buffer_addr_size(upl, buf, sizeof(buf),
374 memmap->region.count, &memmap->region);
375 if (len < 0)
376 return log_msg_ret("buf", len);
377 ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
378 if (!ret && memmap->usage)
379 ret = ofnode_write_bitmask(node, UPLP_USAGE,
380 usage_names,
381 UPLUS_COUNT, memmap->usage);
382 if (ret)
383 return log_msg_ret("lst", ret);
384 }
385
386 return 0;
387}
388
389/**
390 * add_upl_memres() - Add /memory-reserved nodes to the tree
391 *
392 * @upl: UPL state
393 * @root: Parent node to contain the new node
394 * Return 0 if OK, -ve on error
395 */
396static int add_upl_memres(const struct upl *upl, ofnode root,
397 bool skip_existing)
398{
399 ofnode mem_node;
400 int i, ret;
401
402 if (!upl->memmap.count)
403 return 0;
404 ret = ofnode_add_subnode(root, UPLN_MEMORY_RESERVED, &mem_node);
405 if (ret) {
406 if (skip_existing && ret == -EEXIST)
407 return 0;
408 return log_msg_ret("img", ret);
409 }
410
411 for (i = 0; i < upl->memres.count; i++) {
412 const struct upl_memres *memres = alist_get(&upl->memres, i,
413 struct upl_memres);
414 char buf[memres->region.count * sizeof(64) * 2];
415 const struct memregion *first;
416 char name[26];
417 int ret, len;
418 ofnode node;
419
420 if (!memres->region.count) {
421 log_debug("Memory %d has no regions\n", i);
422 return log_msg_ret("reg", -EINVAL);
423 }
424 first = alist_get(&memres->region, 0, struct memregion);
425 sprintf(name, "%s@0x%lx", memres->name, first->base);
426 ret = ofnode_add_subnode(mem_node, name, &node);
427 if (ret)
428 return log_msg_ret("memres", ret);
429
430 len = buffer_addr_size(upl, buf, sizeof(buf),
431 memres->region.count, &memres->region);
432 ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
433 if (!ret && memres->no_map)
434 ret = ofnode_write_bool(node, UPLP_NO_MAP,
435 memres->no_map);
436 if (ret)
437 return log_msg_ret("lst", ret);
438 }
439
440 return 0;
441}
442
443/**
444 * add_upl_serial() - Add serial node
445 *
446 * @upl: UPL state
447 * @root: Parent node to contain the new node
448 * Return 0 if OK, -ve on error
449 */
450static int add_upl_serial(const struct upl *upl, ofnode root,
451 bool skip_existing)
452{
453 const struct upl_serial *ser = &upl->serial;
454 const struct memregion *first;
455 char name[26];
456 ofnode node;
457 int ret;
458
459 if (!ser->compatible || skip_existing)
460 return 0;
461 if (!ser->reg.count)
462 return log_msg_ret("ser", -EINVAL);
463 first = alist_get(&ser->reg, 0, struct memregion);
464 sprintf(name, UPLN_SERIAL "@0x%lx", first->base);
465 ret = ofnode_add_subnode(root, name, &node);
466 if (ret)
467 return log_msg_ret("img", ret);
468 ret = ofnode_write_string(node, UPLP_COMPATIBLE, ser->compatible);
469 if (!ret)
470 ret = ofnode_write_u32(node, UPLP_CLOCK_FREQUENCY,
471 ser->clock_frequency);
472 if (!ret)
473 ret = ofnode_write_u32(node, UPLP_CURRENT_SPEED,
474 ser->current_speed);
475 if (!ret) {
476 char buf[16];
477 int len;
478
479 len = buffer_addr_size(upl, buf, sizeof(buf), 1, &ser->reg);
480 if (len < 0)
481 return log_msg_ret("buf", len);
482
483 ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
484 }
485 if (!ret && ser->reg_io_shift != UPLD_REG_IO_SHIFT)
486 ret = ofnode_write_u32(node, UPLP_REG_IO_SHIFT,
487 ser->reg_io_shift);
488 if (!ret && ser->reg_offset != UPLD_REG_OFFSET)
489 ret = ofnode_write_u32(node, UPLP_REG_OFFSET, ser->reg_offset);
490 if (!ret && ser->reg_io_width != UPLD_REG_IO_WIDTH)
491 ret = ofnode_write_u32(node, UPLP_REG_IO_WIDTH,
492 ser->reg_io_width);
493 if (!ret && ser->virtual_reg)
494 ret = write_addr(upl, node, UPLP_VIRTUAL_REG, ser->virtual_reg);
495 if (!ret) {
496 ret = ofnode_write_value(node, UPLP_ACCESS_TYPE, access_types,
497 ARRAY_SIZE(access_types),
498 ser->access_type);
499 }
500 if (ret)
501 return log_msg_ret("ser", ret);
502
503 return 0;
504}
505
506/**
507 * add_upl_graphics() - Add graphics node
508 *
509 * @upl: UPL state
510 * @root: Parent node to contain the new node
511 * Return 0 if OK, -ve on error
512 */
513static int add_upl_graphics(const struct upl *upl, ofnode root)
514{
515 const struct upl_graphics *gra = &upl->graphics;
516 const struct memregion *first;
517 char name[36];
518 ofnode node;
519 int ret;
520
521 if (!gra->reg.count)
522 return log_msg_ret("gra", -ENOENT);
523 first = alist_get(&gra->reg, 0, struct memregion);
524 sprintf(name, UPLN_GRAPHICS "@0x%lx", first->base);
525 ret = ofnode_add_subnode(root, name, &node);
526 if (ret)
527 return log_msg_ret("gra", ret);
528
529 ret = ofnode_write_string(node, UPLP_COMPATIBLE, UPLC_GRAPHICS);
530 if (!ret) {
531 char buf[16];
532 int len;
533
534 len = buffer_addr_size(upl, buf, sizeof(buf), 1, &gra->reg);
535 if (len < 0)
536 return log_msg_ret("buf", len);
537
538 ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
539 }
540 if (!ret)
541 ret = ofnode_write_u32(node, UPLP_WIDTH, gra->width);
542 if (!ret)
543 ret = ofnode_write_u32(node, UPLP_HEIGHT, gra->height);
544 if (!ret)
545 ret = ofnode_write_u32(node, UPLP_STRIDE, gra->stride);
546 if (!ret) {
547 ret = ofnode_write_value(node, UPLP_GRAPHICS_FORMAT,
548 graphics_formats,
549 ARRAY_SIZE(graphics_formats),
550 gra->format);
551 }
552 if (ret)
553 return log_msg_ret("pro", ret);
554
555 return 0;
556}
557
558int upl_write_handoff(const struct upl *upl, ofnode root, bool skip_existing)
559{
560 ofnode options;
561 int ret;
562
563 ret = add_root_props(upl, root);
564 if (ret)
565 return log_msg_ret("ad1", ret);
566 ret = ofnode_add_subnode(root, UPLN_OPTIONS, &options);
567 if (ret && ret != -EEXIST)
568 return log_msg_ret("opt", -EINVAL);
569
570 ret = add_upl_params(upl, options);
571 if (ret)
572 return log_msg_ret("ad1", ret);
573
574 ret = add_upl_image(upl, options);
575 if (ret)
576 return log_msg_ret("ad2", ret);
577
578 ret = add_upl_memory(upl, root);
579 if (ret)
580 return log_msg_ret("ad3", ret);
581
582 ret = add_upl_memmap(upl, root);
583 if (ret)
584 return log_msg_ret("ad4", ret);
585
586 ret = add_upl_memres(upl, root, skip_existing);
587 if (ret)
588 return log_msg_ret("ad5", ret);
589
590 ret = add_upl_serial(upl, root, skip_existing);
591 if (ret)
592 return log_msg_ret("ad6", ret);
593
594 ret = add_upl_graphics(upl, root);
595 if (ret && ret != -ENOENT)
596 return log_msg_ret("ad6", ret);
597
598 return 0;
599}
600
601int upl_create_handoff_tree(const struct upl *upl, oftree *treep)
602{
603 ofnode root;
604 oftree tree;
605 int ret;
606
607 ret = oftree_new(&tree);
608 if (ret)
609 return log_msg_ret("new", ret);
610
611 root = oftree_root(tree);
612 if (!ofnode_valid(root))
613 return log_msg_ret("roo", -EINVAL);
614
615 ret = upl_write_handoff(upl, root, false);
616 if (ret)
617 return log_msg_ret("wr", ret);
618
619 *treep = tree;
620
621 return 0;
622}