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