Commit | Line | Data |
---|---|---|
cb7a6892 MM |
1 | /* This file is part of the program psim. |
2 | ||
3 | Copyright (C) 1994-1995, Andrew Cagney <cagney@highland.com.au> | |
4 | ||
5 | This program is free software; you can redistribute it and/or modify | |
6 | it under the terms of the GNU General Public License as published by | |
7 | the Free Software Foundation; either version 2 of the License, or | |
8 | (at your option) any later version. | |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | GNU General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License | |
16 | along with this program; if not, write to the Free Software | |
17 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
18 | ||
19 | */ | |
20 | ||
21 | ||
22 | #ifndef _DEVICE_TREE_C_ | |
23 | #define _DEVICE_TREE_C_ | |
24 | ||
25 | #ifndef STATIC_INLINE_DEVICE_TREE | |
26 | #define STATIC_INLINE_DEVICE_TREE STATIC_INLINE | |
27 | #endif | |
28 | ||
29 | #include <string.h> | |
30 | ||
31 | #include "basics.h" | |
32 | #include "device_tree.h" | |
33 | #include "devices.h" | |
34 | ||
35 | #include "bfd.h" | |
36 | ||
37 | enum { clayton_memory_size = 0x100000 }; | |
38 | ||
39 | /* insert the address into the device_nodes sorted list of addresses */ | |
40 | INLINE_DEVICE_TREE void | |
41 | device_node_add_address(device_node *node, | |
42 | unsigned_word lower_bound, | |
43 | unsigned size, | |
44 | device_access access, | |
45 | void *init) | |
46 | { | |
47 | unsigned_word upper_bound = lower_bound + size; | |
48 | device_address *new_address; | |
49 | device_address **current_address; | |
50 | ||
51 | /* find the insertion point */ | |
52 | current_address = &node->addresses; | |
53 | while (*current_address != NULL | |
54 | && (*current_address)->upper_bound >= upper_bound) { | |
55 | current_address = &(*current_address)->next_address; | |
56 | } | |
57 | ||
58 | /* insert */ | |
59 | new_address = ZALLOC(device_address); | |
60 | new_address->lower_bound = lower_bound; | |
61 | new_address->upper_bound = lower_bound + size; | |
62 | new_address->size = size; | |
63 | new_address->access = access; | |
64 | new_address->init = init; | |
65 | new_address->next_address = *current_address; | |
66 | *current_address = new_address; | |
67 | } | |
68 | ||
69 | ||
70 | /* create a new device tree optionally making it a child of the parent | |
71 | node */ | |
72 | ||
73 | INLINE_DEVICE_TREE device_node * | |
74 | device_node_create(device_node *parent, | |
75 | char *name, | |
76 | device_type type, | |
77 | device_callbacks *callbacks, | |
78 | void *data) | |
79 | { | |
80 | device_node *new_node; | |
81 | new_node = ZALLOC(device_node); | |
82 | new_node->parent = parent; | |
83 | new_node->name = name; | |
84 | new_node->type = type; | |
85 | new_node->callbacks = callbacks; | |
86 | new_node->data = data; | |
87 | if (parent != NULL) { | |
88 | new_node->sibling = parent->children; | |
89 | parent->children = new_node; | |
90 | } | |
91 | return new_node; | |
92 | } | |
93 | ||
94 | ||
95 | /* Binary file: | |
96 | ||
97 | The specified file is a binary, assume VEA is required, construct a | |
98 | fake device tree based on the addresses of the text / data segments | |
99 | requested by the binary */ | |
100 | ||
101 | ||
102 | /* Update the fake device tree so that memory is allocated for this | |
103 | section */ | |
104 | STATIC_INLINE_DEVICE_TREE void | |
105 | update_memory_node_for_section(bfd *abfd, | |
106 | asection *the_section, | |
107 | PTR obj) | |
108 | { | |
109 | unsigned_word section_vma; | |
110 | unsigned_word section_size; | |
111 | device_access section_access; | |
112 | void *section_init; | |
113 | device_node *memory = (device_node*)obj; | |
114 | ||
115 | /* skip the section if no memory to allocate */ | |
116 | if (! (bfd_get_section_flags(abfd, the_section) & SEC_ALLOC)) | |
117 | return; | |
118 | ||
119 | /* check/ignore any sections of size zero */ | |
120 | section_size = bfd_get_section_size_before_reloc(the_section); | |
121 | if (section_size == 0) | |
122 | return; | |
123 | ||
124 | /* find where it is to go */ | |
125 | section_vma = bfd_get_section_vma(abfd, the_section); | |
126 | ||
127 | TRACE(trace_device_tree, | |
128 | ("name=%-7s, vma=0x%.8x, size=%6d, flags=%3x(%s%s%s%s )\n", | |
129 | bfd_get_section_name(abfd, the_section), | |
130 | section_vma, section_size, | |
131 | bfd_get_section_flags(abfd, the_section), | |
132 | bfd_get_section_flags(abfd, the_section) & SEC_LOAD ? " LOAD" : "", | |
133 | bfd_get_section_flags(abfd, the_section) & SEC_CODE ? " CODE" : "", | |
134 | bfd_get_section_flags(abfd, the_section) & SEC_DATA ? " DATA" : "", | |
135 | bfd_get_section_flags(abfd, the_section) & SEC_ALLOC ? " ALLOC" : "", | |
136 | bfd_get_section_flags(abfd, the_section) & SEC_READONLY ? " READONLY" : "" | |
137 | )); | |
138 | ||
139 | if (bfd_get_section_flags(abfd, the_section) & SEC_LOAD) { | |
140 | section_init = zalloc(section_size); | |
141 | if (!bfd_get_section_contents(abfd, | |
142 | the_section, | |
143 | section_init, 0, | |
144 | section_size)) { | |
145 | bfd_perror("core:load_section()"); | |
146 | error("load of data failed"); | |
147 | return; | |
148 | } | |
149 | } | |
150 | else { | |
151 | section_init = NULL; | |
152 | } | |
153 | ||
154 | /* determine the devices access */ | |
155 | if (bfd_get_section_flags(abfd, the_section) & SEC_CODE) | |
156 | section_access = (device_is_readable | device_is_executable); | |
157 | else if (bfd_get_section_flags(abfd, the_section) & SEC_READONLY) | |
158 | section_access = device_is_readable; | |
159 | else | |
160 | section_access = (device_is_readable | device_is_writeable); | |
161 | ||
162 | /* find our memory and add this section to its list of addresses */ | |
163 | device_node_add_address(memory, | |
164 | section_vma, | |
165 | section_size, | |
166 | section_access, | |
167 | section_init); | |
168 | } | |
169 | ||
170 | ||
171 | /* construct the device tree from the executable */ | |
172 | ||
173 | STATIC_INLINE_DEVICE_TREE device_node * | |
174 | create_option_device_node(device_node *root, | |
175 | bfd *image) | |
176 | { | |
177 | device_node *option_node; | |
178 | ||
179 | /* the option node and than its members */ | |
180 | option_node = device_node_create(root, "options", options_device, | |
181 | NULL, NULL); | |
182 | ||
183 | /* which endian are we ? */ | |
184 | device_node_create(option_node, | |
185 | "little-endian?", | |
186 | boolean_type_device, | |
187 | NULL, | |
188 | (void*)(image->xvec->byteorder_big_p ? 0 : -1)); | |
189 | ||
190 | /* what is the initial entry point */ | |
191 | device_node_create(option_node, | |
192 | "program-counter", | |
193 | integer_type_device, | |
194 | NULL, | |
195 | (void*)(bfd_get_start_address(image))); | |
196 | ||
197 | /* address of top of boot stack */ | |
198 | TRACE(trace_tbd, ("create_optioin_device_node() - TBD - NT/OpenBoot?\n")); | |
199 | device_node_create(option_node, | |
200 | "stack-pointer", | |
201 | integer_type_device, | |
202 | NULL, | |
203 | (void*)(bfd_get_start_address(image) == 0 | |
204 | ? clayton_memory_size /* OEA */ | |
205 | : (image->xvec->flavour == bfd_target_elf_flavour | |
206 | ? 0xe0000000 /* elf */ | |
207 | : 0x20000000 /* xcoff */))); | |
208 | ||
209 | /* execution environment */ | |
210 | device_node_create(option_node, | |
211 | "vea?", | |
212 | boolean_type_device, | |
213 | NULL, | |
214 | (void*)(bfd_get_start_address(image) == 0 | |
215 | ? 0 | |
216 | : -1)); | |
217 | ||
218 | /* what type of binary */ | |
219 | TRACE(trace_tbd, ("create_optioin_device_node() - TBD - NT/OpenBoot?\n")); | |
220 | device_node_create(option_node, | |
221 | "elf?", | |
222 | boolean_type_device, | |
223 | NULL, | |
224 | (void*)(image->xvec->flavour == bfd_target_elf_flavour | |
225 | ? -1 /* elf binary */ | |
226 | : 0 /* probably aix binary */)); | |
227 | ||
228 | /* must all memory transfers be naturally aligned? */ | |
229 | device_node_create(option_node, | |
230 | "aligned?", | |
231 | boolean_type_device, | |
232 | NULL, | |
233 | (void*)((WITH_ALIGNMENT == NONSTRICT_ALIGNMENT | |
234 | || image->xvec->byteorder_big_p | |
235 | || bfd_get_start_address(image) != 0) | |
236 | ? 0 | |
237 | : -1)); | |
238 | ||
239 | ||
240 | return option_node; | |
241 | } | |
242 | ||
243 | ||
244 | /* clatyon is a simple machine that does not require interrupts or any | |
245 | thing else */ | |
246 | ||
247 | STATIC_INLINE_DEVICE_TREE device_node * | |
248 | create_clayton_device_tree(bfd *image) | |
249 | { | |
250 | device_node *root; | |
251 | device_node *io_node; | |
252 | device_node *data_node; | |
253 | device_node *memory_node; | |
254 | ||
255 | /* the root */ | |
256 | root = ZALLOC(device_node); | |
257 | ||
258 | /* memory - clayton has 2mb of RAM at location 0 */ | |
259 | memory_node = device_node_create(root, | |
260 | "memory", | |
261 | memory_device, | |
262 | NULL, | |
263 | NULL); | |
264 | device_node_add_address(memory_node, 0x0, clayton_memory_size, | |
265 | (device_is_readable | |
266 | | device_is_writeable | |
267 | | device_is_executable), | |
268 | NULL); | |
269 | ||
270 | /* io address space */ | |
271 | io_node = device_node_create(root, "io", bus_device, NULL, NULL); | |
272 | ||
273 | /* and IO devices */ | |
274 | find_device_descriptor("console") | |
275 | ->creator(io_node, "console@0x400000,0"); | |
276 | find_device_descriptor("halt") | |
277 | ->creator(io_node, "halt@0x500000,0"); | |
278 | find_device_descriptor("icu") | |
279 | ->creator(io_node, "icu@0x600000,0"); | |
280 | ||
281 | /* data to load */ | |
282 | data_node = device_node_create(root, "image", data_device, NULL, NULL); | |
283 | bfd_map_over_sections(image, | |
284 | update_memory_node_for_section, | |
285 | (PTR)data_node); | |
286 | ||
287 | /* options */ | |
288 | create_option_device_node(root, image); | |
289 | ||
290 | return root; | |
291 | } | |
292 | ||
293 | ||
294 | /* user mode executable build up a device tree that reflects this */ | |
295 | ||
296 | STATIC_INLINE_DEVICE_TREE device_node * | |
297 | create_vea_device_tree(bfd *image) | |
298 | { | |
299 | device_node *root; | |
300 | device_node *memory_node; | |
301 | device_node *option_node; | |
302 | ||
303 | /* the root */ | |
304 | root = ZALLOC(device_node); | |
305 | ||
306 | /* memory */ | |
307 | memory_node = device_node_create(root, "memory", memory_device, | |
308 | NULL, NULL); | |
309 | bfd_map_over_sections(image, | |
310 | update_memory_node_for_section, | |
311 | (PTR)memory_node); | |
312 | /* options - only endian so far */ | |
313 | option_node = create_option_device_node(root, image); | |
314 | ||
315 | return root; | |
316 | } | |
317 | ||
318 | ||
319 | /* create a device tree from the specified file */ | |
320 | INLINE_DEVICE_TREE device_node * | |
321 | device_tree_create(const char *file_name) | |
322 | { | |
323 | bfd *image; | |
324 | device_node *tree; | |
325 | ||
326 | bfd_init(); /* could be redundant but ... */ | |
327 | ||
328 | /* open the file */ | |
329 | image = bfd_openr(file_name, NULL); | |
330 | if (image == NULL) { | |
331 | bfd_perror("open failed:"); | |
332 | error("nothing loaded\n"); | |
333 | return NULL; | |
334 | } | |
335 | ||
336 | /* check it is valid */ | |
337 | if (!bfd_check_format(image, bfd_object)) { | |
338 | printf_filtered("create_device_tree() - FIXME - should check more bfd bits\n"); | |
339 | printf_filtered("create_device_tree() - %s not an executable, assume device file\n", file_name); | |
340 | bfd_close(image); | |
341 | image = NULL; | |
342 | } | |
343 | ||
344 | /* depending on what was found about the file, load it */ | |
345 | if (image != NULL) { | |
346 | if (bfd_get_start_address(image) == 0) { | |
347 | TRACE(trace_device_tree, ("create_device_tree() - clayton image\n")); | |
348 | tree = create_clayton_device_tree(image); | |
349 | } | |
350 | else if (bfd_get_start_address(image) > 0) { | |
351 | TRACE(trace_device_tree, ("create_device_tree() - vea image\n")); | |
352 | tree = create_vea_device_tree(image); | |
353 | } | |
354 | bfd_close(image); | |
355 | } | |
356 | else { | |
357 | error("TBD - create_device_tree() text file defining device tree\n"); | |
358 | tree = NULL; | |
359 | } | |
360 | ||
361 | return tree; | |
362 | } | |
363 | ||
364 | ||
365 | /* traverse a device tree applying prefix/postfix functions to it */ | |
366 | ||
367 | INLINE_DEVICE_TREE void | |
368 | device_tree_traverse(device_node *root, | |
369 | device_tree_traverse_function *prefix, | |
370 | device_tree_traverse_function *postfix, | |
371 | void *data) | |
372 | { | |
373 | device_node *child; | |
374 | if (prefix != NULL) | |
375 | prefix(root, data); | |
376 | for (child = root->children; child != NULL; child = child->sibling) { | |
377 | device_tree_traverse(child, prefix, postfix, data); | |
378 | } | |
379 | if (postfix != NULL) | |
380 | postfix(root, data); | |
381 | } | |
382 | ||
383 | ||
384 | /* query the device tree */ | |
385 | ||
386 | INLINE_DEVICE_TREE device_node * | |
387 | device_tree_find_node(device_node *root, | |
388 | const char *path) | |
389 | { | |
390 | char *chp; | |
391 | int name_len; | |
392 | device_node *child; | |
393 | ||
394 | /* strip off any leading `/', `../' or `./' */ | |
395 | while (1) { | |
396 | if (strncmp(path, "/", strlen("/")) == 0) { | |
397 | while (root->parent != NULL) | |
398 | root = root->parent; | |
399 | path += strlen("/"); | |
400 | } | |
401 | else if (strncmp(path, "./", strlen("./")) == 0) { | |
402 | root = root; | |
403 | path += strlen("./"); | |
404 | } | |
405 | else if (strncmp(path, "../", strlen("../")) == 0) { | |
406 | if (root->parent != NULL) | |
407 | root = root->parent; | |
408 | path += strlen("../"); | |
409 | } | |
410 | else { | |
411 | break; | |
412 | } | |
413 | } | |
414 | ||
415 | /* find the qualified (with @) and unqualified names in the path */ | |
416 | chp = strchr(path, '/'); | |
417 | name_len = (chp == NULL | |
418 | ? strlen(path) | |
419 | : chp - path); | |
420 | ||
421 | /* search through children for a match */ | |
422 | for (child = root->children; | |
423 | child != NULL; | |
424 | child = child->sibling) { | |
425 | if (strncmp(path, child->name, name_len) == 0 | |
426 | && (strlen(child->name) == name_len | |
427 | || strchr(child->name, '@') == child->name + name_len)) { | |
428 | if (path[name_len] == '\0') | |
429 | return child; | |
430 | else | |
431 | return device_tree_find_node(child, path + name_len + 1); | |
432 | } | |
433 | } | |
434 | return NULL; | |
435 | } | |
436 | ||
437 | INLINE_DEVICE_TREE device_node *device_tree_find_next_node | |
438 | (device_node *root, | |
439 | const char *path, | |
440 | device_node *last); | |
441 | ||
442 | INLINE_DEVICE_TREE signed_word | |
443 | device_tree_find_int(device_node *root, | |
444 | const char *path) | |
445 | { | |
446 | device_node *int_node = device_tree_find_node(root, path); | |
447 | if (int_node == NULL) { | |
448 | error("device_tree_find_int() - node %s does not exist\n", path); | |
449 | return 0; | |
450 | } | |
451 | else if (int_node->type != integer_type_device) { | |
452 | error("device_tree_find_int() - node %s is not an int\n", path); | |
453 | return 0; | |
454 | } | |
455 | else { | |
456 | return (signed_word)(int_node->data); | |
457 | } | |
458 | } | |
459 | ||
460 | ||
461 | INLINE_DEVICE_TREE const char *device_tree_find_string | |
462 | (device_node *root, | |
463 | const char *path); | |
464 | ||
465 | INLINE_DEVICE_TREE int | |
466 | device_tree_find_boolean(device_node *root, | |
467 | const char *path) | |
468 | { | |
469 | device_node *int_node = device_tree_find_node(root, path); | |
470 | if (int_node == NULL) { | |
471 | error("device_tree_find_boolean() - node %s does not exist\n", path); | |
472 | return 0; | |
473 | } | |
474 | else if (int_node->type != boolean_type_device) { | |
475 | error("device_tree_find_boolean() - node %s is not a boolean\n", path); | |
476 | return 0; | |
477 | } | |
478 | else { | |
479 | return (signed_word)(int_node->data); | |
480 | } | |
481 | } | |
482 | ||
483 | ||
484 | INLINE_DEVICE_TREE void *device_tree_find_bytes | |
485 | (device_node *root, | |
486 | const char *path); | |
487 | ||
488 | /* dump out a device node and addresses */ | |
489 | ||
490 | INLINE_DEVICE_TREE void | |
491 | device_tree_dump(device_node *device, | |
492 | void *ignore_data_argument) | |
493 | { | |
494 | printf_filtered("(device_node@0x%x\n", device); | |
495 | printf_filtered(" (parent 0x%x)\n", device->parent); | |
496 | printf_filtered(" (children 0x%x)\n", device->children); | |
497 | printf_filtered(" (sibling 0x%x)\n", device->sibling); | |
498 | printf_filtered(" (name %s)\n", device->name ? device->name : "(null)"); | |
499 | printf_filtered(" (type %d)\n", device->type); | |
500 | printf_filtered(" (handlers 0x%x)\n", device->callbacks); | |
501 | printf_filtered(" (addresses %d)\n", device->addresses); | |
502 | printf_filtered(" (data %d)\n", device->data); | |
503 | printf_filtered(")\n"); | |
504 | } | |
505 | ||
506 | #endif /* _DEVICE_TREE_C_ */ |