514 lines
14 KiB
C
514 lines
14 KiB
C
/* This file is part of the program psim.
|
|
|
|
Copyright (C) 1994-1995, Andrew Cagney <cagney@highland.com.au>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
#ifndef _DEVICE_TREE_C_
|
|
#define _DEVICE_TREE_C_
|
|
|
|
#ifndef STATIC_INLINE_DEVICE_TREE
|
|
#define STATIC_INLINE_DEVICE_TREE STATIC_INLINE
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "basics.h"
|
|
#include "device_tree.h"
|
|
#include "devices.h"
|
|
|
|
#include "bfd.h"
|
|
|
|
/* Any starting address less than this is assumed to be an OEA program
|
|
rather than VEA. */
|
|
#ifndef OEA_START_ADDRESS
|
|
#define OEA_START_ADDRESS 4096
|
|
#endif
|
|
|
|
#ifndef OEA_MEMORY_SIZE
|
|
#define OEA_MEMORY_SIZE 0x100000
|
|
#endif
|
|
|
|
enum { clayton_memory_size = OEA_MEMORY_SIZE };
|
|
|
|
/* insert the address into the device_nodes sorted list of addresses */
|
|
INLINE_DEVICE_TREE void
|
|
device_node_add_address(device_node *node,
|
|
unsigned_word lower_bound,
|
|
unsigned size,
|
|
device_access access,
|
|
void *init)
|
|
{
|
|
unsigned_word upper_bound = lower_bound + size;
|
|
device_address *new_address;
|
|
device_address **current_address;
|
|
|
|
/* find the insertion point */
|
|
current_address = &node->addresses;
|
|
while (*current_address != NULL
|
|
&& (*current_address)->upper_bound >= upper_bound) {
|
|
current_address = &(*current_address)->next_address;
|
|
}
|
|
|
|
/* insert */
|
|
new_address = ZALLOC(device_address);
|
|
new_address->lower_bound = lower_bound;
|
|
new_address->upper_bound = lower_bound + size;
|
|
new_address->size = size;
|
|
new_address->access = access;
|
|
new_address->init = init;
|
|
new_address->next_address = *current_address;
|
|
*current_address = new_address;
|
|
}
|
|
|
|
|
|
/* create a new device tree optionally making it a child of the parent
|
|
node */
|
|
|
|
INLINE_DEVICE_TREE device_node *
|
|
device_node_create(device_node *parent,
|
|
char *name,
|
|
device_type type,
|
|
device_callbacks *callbacks,
|
|
void *data)
|
|
{
|
|
device_node *new_node;
|
|
new_node = ZALLOC(device_node);
|
|
new_node->parent = parent;
|
|
new_node->name = name;
|
|
new_node->type = type;
|
|
new_node->callbacks = callbacks;
|
|
new_node->data = data;
|
|
if (parent != NULL) {
|
|
new_node->sibling = parent->children;
|
|
parent->children = new_node;
|
|
}
|
|
return new_node;
|
|
}
|
|
|
|
|
|
/* Binary file:
|
|
|
|
The specified file is a binary, assume VEA is required, construct a
|
|
fake device tree based on the addresses of the text / data segments
|
|
requested by the binary */
|
|
|
|
|
|
/* Update the fake device tree so that memory is allocated for this
|
|
section */
|
|
STATIC_INLINE_DEVICE_TREE void
|
|
update_memory_node_for_section(bfd *abfd,
|
|
asection *the_section,
|
|
PTR obj)
|
|
{
|
|
unsigned_word section_vma;
|
|
unsigned_word section_size;
|
|
device_access section_access;
|
|
void *section_init;
|
|
device_node *memory = (device_node*)obj;
|
|
|
|
/* skip the section if no memory to allocate */
|
|
if (! (bfd_get_section_flags(abfd, the_section) & SEC_ALLOC))
|
|
return;
|
|
|
|
/* check/ignore any sections of size zero */
|
|
section_size = bfd_get_section_size_before_reloc(the_section);
|
|
if (section_size == 0)
|
|
return;
|
|
|
|
/* find where it is to go */
|
|
section_vma = bfd_get_section_vma(abfd, the_section);
|
|
|
|
TRACE(trace_device_tree,
|
|
("name=%-7s, vma=0x%.8x, size=%6d, flags=%3x(%s%s%s%s )\n",
|
|
bfd_get_section_name(abfd, the_section),
|
|
section_vma, section_size,
|
|
bfd_get_section_flags(abfd, the_section),
|
|
bfd_get_section_flags(abfd, the_section) & SEC_LOAD ? " LOAD" : "",
|
|
bfd_get_section_flags(abfd, the_section) & SEC_CODE ? " CODE" : "",
|
|
bfd_get_section_flags(abfd, the_section) & SEC_DATA ? " DATA" : "",
|
|
bfd_get_section_flags(abfd, the_section) & SEC_ALLOC ? " ALLOC" : "",
|
|
bfd_get_section_flags(abfd, the_section) & SEC_READONLY ? " READONLY" : ""
|
|
));
|
|
|
|
if (bfd_get_section_flags(abfd, the_section) & SEC_LOAD) {
|
|
section_init = zalloc(section_size);
|
|
if (!bfd_get_section_contents(abfd,
|
|
the_section,
|
|
section_init, 0,
|
|
section_size)) {
|
|
bfd_perror("core:load_section()");
|
|
error("load of data failed");
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
section_init = NULL;
|
|
}
|
|
|
|
/* determine the devices access */
|
|
if (bfd_get_section_flags(abfd, the_section) & SEC_CODE)
|
|
section_access = (device_is_readable | device_is_executable);
|
|
else if (bfd_get_section_flags(abfd, the_section) & SEC_READONLY)
|
|
section_access = device_is_readable;
|
|
else
|
|
section_access = (device_is_readable | device_is_writeable);
|
|
|
|
/* find our memory and add this section to its list of addresses */
|
|
device_node_add_address(memory,
|
|
section_vma,
|
|
section_size,
|
|
section_access,
|
|
section_init);
|
|
}
|
|
|
|
|
|
/* construct the device tree from the executable */
|
|
|
|
STATIC_INLINE_DEVICE_TREE device_node *
|
|
create_option_device_node(device_node *root,
|
|
bfd *image)
|
|
{
|
|
int oea = (bfd_get_start_address(image) < OEA_START_ADDRESS);
|
|
int elf = (image->xvec->flavour == bfd_target_elf_flavour);
|
|
device_node *option_node;
|
|
|
|
/* the option node and than its members */
|
|
option_node = device_node_create(root, "options", options_device,
|
|
NULL, NULL);
|
|
|
|
/* which endian are we ? */
|
|
device_node_create(option_node,
|
|
"little-endian?",
|
|
boolean_type_device,
|
|
NULL,
|
|
(void*)(image->xvec->byteorder_big_p ? 0 : -1));
|
|
|
|
/* what is the initial entry point */
|
|
device_node_create(option_node,
|
|
"program-counter",
|
|
integer_type_device,
|
|
NULL,
|
|
(void*)(bfd_get_start_address(image)));
|
|
|
|
/* address of top of boot stack */
|
|
TRACE(trace_tbd, ("create_optioin_device_node() - TBD - NT/OpenBoot?\n"));
|
|
device_node_create(option_node,
|
|
"stack-pointer",
|
|
integer_type_device,
|
|
NULL,
|
|
(void *)((oea)
|
|
? clayton_memory_size /* OEA */
|
|
: ((elf)
|
|
? 0xe0000000 /* elf */
|
|
: 0x20000000 /* xcoff */)));
|
|
|
|
/* execution environment */
|
|
device_node_create(option_node,
|
|
"vea?",
|
|
boolean_type_device,
|
|
NULL,
|
|
(void *)((oea) ? 0 : -1));
|
|
|
|
/* what type of binary */
|
|
TRACE(trace_tbd, ("create_optioin_device_node() - TBD - NT/OpenBoot?\n"));
|
|
device_node_create(option_node,
|
|
"elf?",
|
|
boolean_type_device,
|
|
NULL,
|
|
(void *)((elf) ? -1 : 0));
|
|
|
|
/* must all memory transfers be naturally aligned? */
|
|
device_node_create(option_node,
|
|
"aligned?",
|
|
boolean_type_device,
|
|
NULL,
|
|
(void*)((WITH_ALIGNMENT == NONSTRICT_ALIGNMENT
|
|
|| image->xvec->byteorder_big_p
|
|
|| !oea)
|
|
? 0
|
|
: -1));
|
|
|
|
|
|
return option_node;
|
|
}
|
|
|
|
|
|
/* clatyon is a simple machine that does not require interrupts or any
|
|
thing else */
|
|
|
|
STATIC_INLINE_DEVICE_TREE device_node *
|
|
create_clayton_device_tree(bfd *image)
|
|
{
|
|
device_node *root;
|
|
device_node *io_node;
|
|
device_node *data_node;
|
|
device_node *memory_node;
|
|
|
|
/* the root */
|
|
root = ZALLOC(device_node);
|
|
|
|
/* memory - clayton has 2mb of RAM at location 0 */
|
|
memory_node = device_node_create(root,
|
|
"memory",
|
|
memory_device,
|
|
NULL,
|
|
NULL);
|
|
device_node_add_address(memory_node, 0x0, clayton_memory_size,
|
|
(device_is_readable
|
|
| device_is_writeable
|
|
| device_is_executable),
|
|
NULL);
|
|
|
|
/* io address space */
|
|
io_node = device_node_create(root, "io", bus_device, NULL, NULL);
|
|
|
|
/* and IO devices */
|
|
find_device_descriptor("console")
|
|
->creator(io_node, "console@0x400000,0");
|
|
find_device_descriptor("halt")
|
|
->creator(io_node, "halt@0x500000,0");
|
|
find_device_descriptor("icu")
|
|
->creator(io_node, "icu@0x600000,0");
|
|
|
|
/* data to load */
|
|
data_node = device_node_create(root, "image", data_device, NULL, NULL);
|
|
bfd_map_over_sections(image,
|
|
update_memory_node_for_section,
|
|
(PTR)data_node);
|
|
|
|
/* options */
|
|
create_option_device_node(root, image);
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
/* user mode executable build up a device tree that reflects this */
|
|
|
|
STATIC_INLINE_DEVICE_TREE device_node *
|
|
create_vea_device_tree(bfd *image)
|
|
{
|
|
device_node *root;
|
|
device_node *memory_node;
|
|
device_node *option_node;
|
|
|
|
/* the root */
|
|
root = ZALLOC(device_node);
|
|
|
|
/* memory */
|
|
memory_node = device_node_create(root, "memory", memory_device,
|
|
NULL, NULL);
|
|
bfd_map_over_sections(image,
|
|
update_memory_node_for_section,
|
|
(PTR)memory_node);
|
|
/* options - only endian so far */
|
|
option_node = create_option_device_node(root, image);
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
/* create a device tree from the specified file */
|
|
INLINE_DEVICE_TREE device_node *
|
|
device_tree_create(const char *file_name)
|
|
{
|
|
bfd *image;
|
|
device_node *tree;
|
|
|
|
bfd_init(); /* could be redundant but ... */
|
|
|
|
/* open the file */
|
|
image = bfd_openr(file_name, NULL);
|
|
if (image == NULL) {
|
|
bfd_perror("open failed:");
|
|
error("nothing loaded\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* check it is valid */
|
|
if (!bfd_check_format(image, bfd_object)) {
|
|
printf_filtered("create_device_tree() - FIXME - should check more bfd bits\n");
|
|
printf_filtered("create_device_tree() - %s not an executable, assume device file\n", file_name);
|
|
bfd_close(image);
|
|
image = NULL;
|
|
}
|
|
|
|
/* depending on what was found about the file, load it */
|
|
if (image != NULL) {
|
|
if (bfd_get_start_address(image) == 0) {
|
|
TRACE(trace_device_tree, ("create_device_tree() - clayton image\n"));
|
|
tree = create_clayton_device_tree(image);
|
|
}
|
|
else if (bfd_get_start_address(image) > 0) {
|
|
TRACE(trace_device_tree, ("create_device_tree() - vea image\n"));
|
|
tree = create_vea_device_tree(image);
|
|
}
|
|
bfd_close(image);
|
|
}
|
|
else {
|
|
error("TBD - create_device_tree() text file defining device tree\n");
|
|
tree = NULL;
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
|
|
/* traverse a device tree applying prefix/postfix functions to it */
|
|
|
|
INLINE_DEVICE_TREE void
|
|
device_tree_traverse(device_node *root,
|
|
device_tree_traverse_function *prefix,
|
|
device_tree_traverse_function *postfix,
|
|
void *data)
|
|
{
|
|
device_node *child;
|
|
if (prefix != NULL)
|
|
prefix(root, data);
|
|
for (child = root->children; child != NULL; child = child->sibling) {
|
|
device_tree_traverse(child, prefix, postfix, data);
|
|
}
|
|
if (postfix != NULL)
|
|
postfix(root, data);
|
|
}
|
|
|
|
|
|
/* query the device tree */
|
|
|
|
INLINE_DEVICE_TREE device_node *
|
|
device_tree_find_node(device_node *root,
|
|
const char *path)
|
|
{
|
|
char *chp;
|
|
int name_len;
|
|
device_node *child;
|
|
|
|
/* strip off any leading `/', `../' or `./' */
|
|
while (1) {
|
|
if (strncmp(path, "/", strlen("/")) == 0) {
|
|
while (root->parent != NULL)
|
|
root = root->parent;
|
|
path += strlen("/");
|
|
}
|
|
else if (strncmp(path, "./", strlen("./")) == 0) {
|
|
root = root;
|
|
path += strlen("./");
|
|
}
|
|
else if (strncmp(path, "../", strlen("../")) == 0) {
|
|
if (root->parent != NULL)
|
|
root = root->parent;
|
|
path += strlen("../");
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* find the qualified (with @) and unqualified names in the path */
|
|
chp = strchr(path, '/');
|
|
name_len = (chp == NULL
|
|
? strlen(path)
|
|
: chp - path);
|
|
|
|
/* search through children for a match */
|
|
for (child = root->children;
|
|
child != NULL;
|
|
child = child->sibling) {
|
|
if (strncmp(path, child->name, name_len) == 0
|
|
&& (strlen(child->name) == name_len
|
|
|| strchr(child->name, '@') == child->name + name_len)) {
|
|
if (path[name_len] == '\0')
|
|
return child;
|
|
else
|
|
return device_tree_find_node(child, path + name_len + 1);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
INLINE_DEVICE_TREE device_node *device_tree_find_next_node
|
|
(device_node *root,
|
|
const char *path,
|
|
device_node *last);
|
|
|
|
INLINE_DEVICE_TREE signed_word
|
|
device_tree_find_int(device_node *root,
|
|
const char *path)
|
|
{
|
|
device_node *int_node = device_tree_find_node(root, path);
|
|
if (int_node == NULL) {
|
|
error("device_tree_find_int() - node %s does not exist\n", path);
|
|
return 0;
|
|
}
|
|
else if (int_node->type != integer_type_device) {
|
|
error("device_tree_find_int() - node %s is not an int\n", path);
|
|
return 0;
|
|
}
|
|
else {
|
|
return (signed_word)(int_node->data);
|
|
}
|
|
}
|
|
|
|
|
|
INLINE_DEVICE_TREE const char *device_tree_find_string
|
|
(device_node *root,
|
|
const char *path);
|
|
|
|
INLINE_DEVICE_TREE int
|
|
device_tree_find_boolean(device_node *root,
|
|
const char *path)
|
|
{
|
|
device_node *int_node = device_tree_find_node(root, path);
|
|
if (int_node == NULL) {
|
|
error("device_tree_find_boolean() - node %s does not exist\n", path);
|
|
return 0;
|
|
}
|
|
else if (int_node->type != boolean_type_device) {
|
|
error("device_tree_find_boolean() - node %s is not a boolean\n", path);
|
|
return 0;
|
|
}
|
|
else {
|
|
return (signed_word)(int_node->data);
|
|
}
|
|
}
|
|
|
|
|
|
INLINE_DEVICE_TREE void *device_tree_find_bytes
|
|
(device_node *root,
|
|
const char *path);
|
|
|
|
/* dump out a device node and addresses */
|
|
|
|
INLINE_DEVICE_TREE void
|
|
device_tree_dump(device_node *device,
|
|
void *ignore_data_argument)
|
|
{
|
|
printf_filtered("(device_node@0x%x\n", device);
|
|
printf_filtered(" (parent 0x%x)\n", device->parent);
|
|
printf_filtered(" (children 0x%x)\n", device->children);
|
|
printf_filtered(" (sibling 0x%x)\n", device->sibling);
|
|
printf_filtered(" (name %s)\n", device->name ? device->name : "(null)");
|
|
printf_filtered(" (type %d)\n", device->type);
|
|
printf_filtered(" (handlers 0x%x)\n", device->callbacks);
|
|
printf_filtered(" (addresses %d)\n", device->addresses);
|
|
printf_filtered(" (data %d)\n", device->data);
|
|
printf_filtered(")\n");
|
|
}
|
|
|
|
#endif /* _DEVICE_TREE_C_ */
|