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 }
218