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 | |
15 | static 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 | |
24 | static 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 | |
33 | static 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 | |
42 | static 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 | |
51 | static 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 | |
62 | static 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 | |
73 | static 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 | |
104 | static 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 | */ |
130 | int 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 | |