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