1// SPDX-License-Identifier: GPL-2.0
2#include <linux/libfdt_env.h>
3#include <asm/setup.h>
4#include <libfdt.h>
5#include "misc.h"
6
7#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND)
8#define do_extend_cmdline 1
9#else
10#define do_extend_cmdline 0
11#endif
12
13#define NR_BANKS 16
14
15static int node_offset(void *fdt, const char *node_path)
16{
17 int offset = fdt_path_offset(fdt, node_path);
18 if (offset == -FDT_ERR_NOTFOUND)
19 /* Add the node to root if not found, dropping the leading '/' */
20 offset = fdt_add_subnode(fdt, 0, node_path + 1);
21 return offset;
22}
23
24static int setprop(void *fdt, const char *node_path, const char *property,
25 void *val_array, int size)
26{
27 int offset = node_offset(fdt, node_path);
28 if (offset < 0)
29 return offset;
30 return fdt_setprop(fdt, offset, property, val_array, size);
31}
32
33static int setprop_string(void *fdt, const char *node_path,
34 const char *property, const char *string)
35{
36 int offset = node_offset(fdt, node_path);
37 if (offset < 0)
38 return offset;
39 return fdt_setprop_string(fdt, offset, property, string);
40}
41
42static int setprop_cell(void *fdt, const char *node_path,
43 const char *property, uint32_t val)
44{
45 int offset = node_offset(fdt, node_path);
46 if (offset < 0)
47 return offset;
48 return fdt_setprop_cell(fdt, offset, property, val);
49}
50
51static const void *getprop(const void *fdt, const char *node_path,
52 const char *property, int *len)
53{
54 int offset = fdt_path_offset(fdt, node_path);
55
56 if (offset == -FDT_ERR_NOTFOUND)
57 return NULL;
58
59 return fdt_getprop(fdt, offset, property, len);
60}
61
62static uint32_t get_cell_size(const void *fdt)
63{
64 int len;
65 uint32_t cell_size = 1;
66 const __be32 *size_len = getprop(fdt, node_path: "/", property: "#size-cells", len: &len);
67
68 if (size_len)
69 cell_size = fdt32_to_cpu(*size_len);
70 return cell_size;
71}
72
73static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline)
74{
75 char cmdline[COMMAND_LINE_SIZE];
76 const char *fdt_bootargs;
77 char *ptr = cmdline;
78 int len = 0;
79
80 /* copy the fdt command line into the buffer */
81 fdt_bootargs = getprop(fdt, node_path: "/chosen", property: "bootargs", len: &len);
82 if (fdt_bootargs)
83 if (len < COMMAND_LINE_SIZE) {
84 memcpy(ptr, fdt_bootargs, len);
85 /* len is the length of the string
86 * including the NULL terminator */
87 ptr += len - 1;
88 }
89
90 /* and append the ATAG_CMDLINE */
91 if (fdt_cmdline) {
92 len = strlen(fdt_cmdline);
93 if (ptr - cmdline + len + 2 < COMMAND_LINE_SIZE) {
94 *ptr++ = ' ';
95 memcpy(ptr, fdt_cmdline, len);
96 ptr += len;
97 }
98 }
99 *ptr = '\0';
100
101 setprop_string(fdt, node_path: "/chosen", property: "bootargs", string: cmdline);
102}
103
104static void hex_str(char *out, uint32_t value)
105{
106 uint32_t digit;
107 int idx;
108
109 for (idx = 7; idx >= 0; idx--) {
110 digit = value >> 28;
111 value <<= 4;
112 digit &= 0xf;
113 if (digit < 10)
114 digit += '0';
115 else
116 digit += 'A'-10;
117 *out++ = digit;
118 }
119 *out = '\0';
120}
121
122/*
123 * Convert and fold provided ATAGs into the provided FDT.
124 *
125 * Return values:
126 * = 0 -> pretend success
127 * = 1 -> bad ATAG (may retry with another possible ATAG pointer)
128 * < 0 -> error from libfdt
129 */
130int atags_to_fdt(void *atag_list, void *fdt, int total_space)
131{
132 struct tag *atag = atag_list;
133 /* In the case of 64 bits memory size, need to reserve 2 cells for
134 * address and size for each bank */
135 __be32 mem_reg_property[2 * 2 * NR_BANKS];
136 int memcount = 0;
137 int ret, memsize;
138
139 /* make sure we've got an aligned pointer */
140 if ((u32)atag_list & 0x3)
141 return 1;
142
143 /* if we get a DTB here we're done already */
144 if (*(__be32 *)atag_list == cpu_to_fdt32(FDT_MAGIC))
145 return 0;
146
147 /* validate the ATAG */
148 if (atag->hdr.tag != ATAG_CORE ||
149 (atag->hdr.size != tag_size(tag_core) &&
150 atag->hdr.size != 2))
151 return 1;
152
153 /* let's give it all the room it could need */
154 ret = fdt_open_into(fdt, fdt, total_space);
155 if (ret < 0)
156 return ret;
157
158 for_each_tag(atag, atag_list) {
159 if (atag->hdr.tag == ATAG_CMDLINE) {
160 /* Append the ATAGS command line to the device tree
161 * command line.
162 * NB: This means that if the same parameter is set in
163 * the device tree and in the tags, the one from the
164 * tags will be chosen.
165 */
166 if (do_extend_cmdline)
167 merge_fdt_bootargs(fdt,
168 fdt_cmdline: atag->u.cmdline.cmdline);
169 else
170 setprop_string(fdt, node_path: "/chosen", property: "bootargs",
171 string: atag->u.cmdline.cmdline);
172 } else if (atag->hdr.tag == ATAG_MEM) {
173 if (memcount >= sizeof(mem_reg_property)/4)
174 continue;
175 if (!atag->u.mem.size)
176 continue;
177 memsize = get_cell_size(fdt);
178
179 if (memsize == 2) {
180 /* if memsize is 2, that means that
181 * each data needs 2 cells of 32 bits,
182 * so the data are 64 bits */
183 __be64 *mem_reg_prop64 =
184 (__be64 *)mem_reg_property;
185 mem_reg_prop64[memcount++] =
186 cpu_to_fdt64(atag->u.mem.start);
187 mem_reg_prop64[memcount++] =
188 cpu_to_fdt64(atag->u.mem.size);
189 } else {
190 mem_reg_property[memcount++] =
191 cpu_to_fdt32(atag->u.mem.start);
192 mem_reg_property[memcount++] =
193 cpu_to_fdt32(atag->u.mem.size);
194 }
195
196 } else if (atag->hdr.tag == ATAG_INITRD2) {
197 uint32_t initrd_start, initrd_size;
198 initrd_start = atag->u.initrd.start;
199 initrd_size = atag->u.initrd.size;
200 setprop_cell(fdt, node_path: "/chosen", property: "linux,initrd-start",
201 val: initrd_start);
202 setprop_cell(fdt, node_path: "/chosen", property: "linux,initrd-end",
203 val: initrd_start + initrd_size);
204 } else if (atag->hdr.tag == ATAG_SERIAL) {
205 char serno[16+2];
206 hex_str(out: serno, value: atag->u.serialnr.high);
207 hex_str(out: serno+8, value: atag->u.serialnr.low);
208 setprop_string(fdt, node_path: "/", property: "serial-number", string: serno);
209 }
210 }
211
212 if (memcount) {
213 setprop(fdt, node_path: "/memory", property: "reg", val_array: mem_reg_property,
214 size: 4 * memcount * memsize);
215 }
216
217 return fdt_pack(fdt);
218}
219

source code of linux/arch/arm/boot/compressed/atags_to_fdt.c