##############################################################################
# Australian Public Licence B (OZPLB)
# 
# Version 1-0
# 
# Copyright (c) 2007, Open Kernel Labs, Inc.
# 
# All rights reserved. 
# 
# Developed by: Embedded, Real-time and Operating Systems Program (ERTOS)
#               National ICT Australia
#               http://www.ertos.nicta.com.au
# 
# Permission is granted by National ICT Australia, free of charge, to
# any person obtaining a copy of this software and any associated
# documentation files (the "Software") to deal with the Software without
# restriction, including (without limitation) the rights to use, copy,
# modify, adapt, merge, publish, distribute, communicate to the public,
# sublicense, and/or sell, lend or rent out copies of the Software, and
# to permit persons to whom the Software is furnished to do so, subject
# to the following conditions:
# 
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimers.
# 
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimers in the documentation and/or other materials provided
#       with the distribution.
# 
#     * Neither the name of National ICT Australia, nor the names of its
#       contributors, may be used to endorse or promote products derived
#       from this Software without specific prior written permission.
# 
# EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT
# PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS", AND
# NATIONAL ICT AUSTRALIA AND ITS CONTRIBUTORS MAKE NO REPRESENTATIONS,
# WARRANTIES OR CONDITIONS OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO ANY REPRESENTATIONS, WARRANTIES OR CONDITIONS
# REGARDING THE CONTENTS OR ACCURACY OF THE SOFTWARE, OR OF TITLE,
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT,
# THE ABSENCE OF LATENT OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF
# ERRORS, WHETHER OR NOT DISCOVERABLE.
# 
# TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL
# NATIONAL ICT AUSTRALIA OR ITS CONTRIBUTORS BE LIABLE ON ANY LEGAL
# THEORY (INCLUDING, WITHOUT LIMITATION, IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM, LOSS, DAMAGES OR OTHER
# LIABILITY, INCLUDING (WITHOUT LIMITATION) LOSS OF PRODUCTION OR
# OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF DATA OR RECORDS; OR LOSS
# OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR
# OTHER ECONOMIC LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT,
# CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR IN
# CONNECTION WITH THIS LICENCE, THE SOFTWARE OR THE USE OF OR OTHER
# DEALINGS WITH THE SOFTWARE, EVEN IF NATIONAL ICT AUSTRALIA OR ITS
# CONTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS,
# DAMAGES OR OTHER LIABILITY.
# 
# If applicable legislation implies representations, warranties, or
# conditions, or imposes obligations or liability on National ICT
# Australia or one of its contributors in respect of the Software that
# cannot be wholly or partly excluded, restricted or modified, the
# liability of National ICT Australia or the contributor is limited, to
# the full extent permitted by the applicable legislation, at its
# option, to:
# a.  in the case of goods, any one or more of the following:
# i.  the replacement of the goods or the supply of equivalent goods;
# ii.  the repair of the goods;
# iii. the payment of the cost of replacing the goods or of acquiring
#  equivalent goods;
# iv.  the payment of the cost of having the goods repaired; or
# b.  in the case of services:
# i.  the supplying of the services again; or
# ii.  the payment of the cost of having the services supplied again.
# 
# The construction, validity and performance of this licence is governed
# by the laws in force in New South Wales, Australia.

"""
Define os_bootinfo_el.
"""
from elf.core import ElfFile
from elf.constants import ET_EXEC
from weaver import MergeError
from weaver.ezxml import ParsedElement, Element, long_attr, bool_attr, str_attr
from weaver.segments_xml import collect_patches, collect_elf_segments, \
     attach_to_elf_flags, Segment_el, Heap_el, Patch_el, \
     start_to_value, make_pager_attr
from weaver.memobjs_xml import Stack_el, Memsection_el, \
     collect_memsection_element, create_standard_caps
import weaver.machine
import weaver.pools
import weaver.image
import weaver.namespace
import weaver.bootinfo

Argv_el = Element("arg",
                  value = (str_attr, "required"))

CommandLine_el = Element("commandline", Argv_el)

Thread_el = Element("thread", Stack_el, CommandLine_el,
                    name     = (str_attr, "required"),
                    start    = (str_attr, "required"),
                    priority = (long_attr, "optional"),
                    physpool = (str_attr, "optional"),
                    virtpool = (str_attr, "optional"))

Entry_el = Element("entry",
                        key    = (str_attr, "required"),
                        value  = (long_attr, "optional"),
                        cap    = (str_attr, "optional"),
                        attach = (str_attr, "optional"))

Environment_el = Element("environment", Entry_el)

Zone_el = Element("zone", Segment_el, Memsection_el, 
                  name     = (str_attr, "required"),
                  physpool = (str_attr, "optional"),
                  virtpool = (str_attr, "optional"))

                       
Program_el = Element("program", 
                     Segment_el, Patch_el, Stack_el, Heap_el, CommandLine_el,
                     Memsection_el, Thread_el, Environment_el, Zone_el,
                     name     = (str_attr, "required"),
                     file     = (str_attr, "required"),
                     priority = (long_attr, "optional"),
                     physpool = (str_attr, "optional"),
                     virtpool = (str_attr, "optional"),
                     direct   = (bool_attr, "optional"),
                     pager    = (str_attr, "optional"),
                     server   = (str_attr, "optional"),
                     )

PD_el = Element("pd",
                Segment_el, Patch_el, Memsection_el, Thread_el,
                Environment_el, Zone_el,
                name     = (str_attr, "required"),
                file     = (str_attr, "optional"),
                physpool = (str_attr, "optional"),
                virtpool = (str_attr, "optional"),
                pager    = (str_attr, "optional"),
                direct   = (bool_attr, "optional"),
                )


def collect_thread(elf, el, ignore_name, namespace, entry,
                   name = None, namespace_thread_name = None):
    """Collect the attributes of a thread element."""
    if entry is None:
        raise MergeError, "No entry point specified for thread %s" % name

    # el can be a program element or a thread element.
    if name is None:
        name = el.name

    user_main = None
    if hasattr(el, 'start'):
        user_main = el.start

    # HACK:  There is no easy way for a PD to specify the main thread,
    # so is the user entry point is '_start' (the traditional entry
    # point symbol), then start there rather then the thread entry
    # point.
    if user_main == "_start":
        start = "_start"
        user_main = None

    entry = start_to_value(entry, elf)
    user_main = start_to_value(user_main, elf)

    priority = None
    if hasattr(el, 'priority'):
        priority = el.priority

    physpool = None
    if hasattr(el, 'physpool'):
        physpool = el.physpool

    virtpool = None
    if hasattr(el, 'virtpool'):
        virtpool = el.virtpool

    # New namespace for objects living in the thread.
    if namespace_thread_name is None:
        namespace_thread_name = name
    new_namespace = namespace.add_namespace(namespace_thread_name)

    # Push the overriding pools for the thread.
    weaver.image.image.push_attrs(virtual = virtpool,
                                  physical = physpool)

    # Create the thread desription object.
    thread = weaver.bootinfo.Thread(name, entry, user_main, priority)

    # Add the standard caps for the thread.
    create_standard_caps(thread, new_namespace)

    # Collect the stack.  Is there no element, create a fake one for
    # the collection code to use.
    stack_el = el.find_child('stack')

    if stack_el is None:
        stack_el = ParsedElement('stack')
    
    stack_ms = collect_memsection_element(stack_el, ignore_name, new_namespace)
    thread.attach_stack(stack_ms)
    weaver.image.image.add_group(0, [stack_ms.get_ms()])

    # Collect any command line arguments.
    commandline_el = el.find_child('commandline')

    if commandline_el is not None:
        for arg_el in commandline_el.find_children("arg"):
            thread.add_argv(arg_el.value)

    weaver.image.image.pop_attrs()

    return thread

def collect_environment_element(env_el, namespace):
    """Collect the object environment for a program or PD."""
    env = weaver.bootinfo.Environment(namespace)

    # Collect any custom entries in the environment.
    if env_el is not None:
        for entry_el in env_el.find_children('entry'):
            value  = None
            cap    = None
            attach = None

            if hasattr(entry_el, 'value'):
                value = entry_el.value
            else:
                if not hasattr(entry_el, 'cap'):
                    raise 'Value or cap attribute required.'

                cap = entry_el.cap

                if hasattr(entry_el, 'attach'):
                    attach = attach_to_elf_flags(entry_el.attach)

            env.add_entry(entry_el.key, value = value,
                          cap_name = cap, attach = attach)

            env.add_pd_extension_token_entry(entry_el.key + "_PD_EXT",
                    0, 0, 0, 0)

    return env

def collect_zone_element(zone_el, ignore_name, namespace):
    """Collect the contents of a zone."""
    non_zone_ms = []
    env = weaver.bootinfo.Environment(namespace)

    # New namespace for objects living in the memsection.
    zone_namespace = namespace.add_namespace(zone_el.name)

    virtpool = getattr(zone_el, 'virtpool', None)
    physpool = getattr(zone_el, 'physpool', None)

    attrs = weaver.image.image.new_attrs(zone_namespace)
    attrs.name = zone_el.name

    zone = weaver.bootinfo.Zone(attrs)

    weaver.bootinfo.bootinfo.add_zone(zone)

    # Push the overriding attributes for the zone.
    weaver.image.image.push_attrs(virtual  = zone_el.name,
                                  physical = physpool)
    
    master = weaver.bootinfo.Cap("master", ["master"])
    zone.add_cap(master)
    zone_namespace.add(master.get_name(), master)

    for ms_el in zone_el.find_children('memsection'):
        if not ignore_name.match(ms_el.name):
            ms = collect_memsection_element(ms_el, ignore_name, zone_namespace)

            # Memsections that aren't allocated in the zone belong to the PD.
            if ms.get_attrs().virtpool == zone_el.name:
                zone.add_memsection(ms)
                weaver.image.image.add_group(None, [ms.get_ms()])
            else:
                non_zone_ms.append(ms)

    # All of the segments have already been processed by the program/pd, but
    # record the ones probably should belong to the zone.  These will be added
    # to the zone by PD.add_zone()
    segment_els = zone_el.find_children("segment")
    zone.set_claimed_segments([segment_el.name for segment_el in segment_els])

    weaver.image.image.pop_attrs()

    return (zone, non_zone_ms)

def collect_program_element(program_el, ignore_name, namespace):
    """Collect the attributes of a program element."""

    # New namespace for objects living in the program's PD.
    prog_namespace = namespace.add_namespace(program_el.name)

    pd = weaver.bootinfo.PD(program_el.name, prog_namespace)

    elf = ElfFile.from_filename(program_el.file)

    if elf.get_elf_type() != ET_EXEC:
        raise MergeError, "All the merged ELF files must be of EXEC type."

    weaver.bootinfo.bootinfo.add_elf_info(name = program_el.file,
                                   elf_type = weaver.image.image.PROGRAM,
                                   entry_point = elf.get_entry_point())

    virtpool = getattr(program_el, 'virtpool', None)
    physpool = getattr(program_el, 'physpool', None)
    direct   = getattr(program_el, 'direct',   None)

    if hasattr(program_el, 'pager'):
        pager = make_pager_attr(program_el.pager)
    else:
        pager = None

    # Push the overriding attributes for the program.
    weaver.image.image.push_attrs(virtual  = virtpool,
                                  physical = physpool,
                                  pager    = pager,
                                  direct   = direct)

    pd.set_default_pools()

    # Collect the object environment
    env = collect_environment_element(program_el.find_child('environment'),
                                      prog_namespace)

    segment_els = program_el.find_all_children("segment")
    segs = collect_elf_segments(elf,
                                weaver.image.image.PROGRAM,
                                segment_els,
                                program_el.name,
                                prog_namespace)

    segs_ms = [weaver.bootinfo.bootinfo.record_segment_info(program_el.name, seg) for seg in segs]
    for seg_ms in segs_ms:
        pd.attach_memsection(seg_ms)
        if seg_ms.get_attrs().protected:

            seg_namespace = prog_namespace.add_namespace(seg_ms.get_name())
            create_standard_caps(seg_ms, seg_namespace)

            env.add_entry(key = seg_ms.get_name(),
                          cap_name = seg_ms.get_name() + '/master',
                          attach = 0)

    # Record any patches being made to the program.
    patch_els   = program_el.find_children("patch")
    collect_patches(elf, patch_els, program_el.file)

    # Collect the main thread.
    thread = collect_thread(elf, program_el, ignore_name, prog_namespace,
                            entry = elf.get_entry_point(),
                            name = program_el.name,
                            namespace_thread_name = "main")
    pd.add_thread(thread)
    pd.set_server_thread(thread)

    # Record the main thread and its stack in the environment.
    env.add_entry(key      = "MAIN",
                  cap_name = 'main/master')
    env.add_entry(key      = "MAIN/STACK",
                  cap_name = 'main/stack/rw',
                  attach  = thread.get_stack().get_attrs().attach)


    # If marked, sure that the program is exported to every
    # environment so that it can be found by other programs.
    # 
    if hasattr(program_el, 'server'):
        weaver.bootinfo.bootinfo.add_server(
            key = program_el.server,
            cap_name = prog_namespace.abs_name('main') + '/stack/master')

    # Collect remaining threads.
    for thread_el in program_el.find_children('thread'):
        if not ignore_name.match(thread_el.name):
            thread = collect_thread(elf, thread_el, ignore_name,
                                    prog_namespace,
                                    entry = "_thread_start")
            pd.add_thread(thread)

            # Record the thread and its stack in the environment.
            env.add_entry(key      = thread.get_name(),
                          cap_name = thread.get_name() + '/master')
            env.add_entry(key      = thread.get_name() + "/STACK",
                          cap_name = thread.get_name() + '/stack/rw',
                          attach   = thread.get_stack().get_attrs().attach)
         

    # Collect any other memsections in the program.
    for ms_el in program_el.find_children('memsection'):
        if not ignore_name.match(ms_el.name):
            ms = collect_memsection_element(ms_el, ignore_name, prog_namespace)
        
            pd.attach_memsection(ms)
            weaver.image.image.add_group(0, [ms.get_ms()])
            if ms.get_name() == 'SMEM':
                env.add_entry(key      = ms.get_name(),
                              cap_name = ms.get_name() + '/rw',
                              attach   = ms.get_attrs().attach)
            else:
                env.add_entry(key      = ms.get_name(),
                              cap_name = ms.get_name() + '/master',
                              attach   = ms.get_attrs().attach)


    # Collect any zones in the program.
    for zone_el in program_el.find_children('zone'):
        (zone, non_zone_ms) = \
               collect_zone_element(zone_el, ignore_name, prog_namespace)

        pd.attach_zone(zone)

        # Attach memsections that aren't part of the zone to the program.
        for ms in non_zone_ms:
            pd.attach_memsection(ms)

            weaver.image.image.add_group(0, [ms.get_ms()])
            env.add_entry(key      = ms.get_name(),
                          cap_name = ms.get_name() + '/master',
                          attach   = ms.get_attrs().attach)
            

    # Collect the heap.  Is there no element, create a fake one for
    # the collection code to use.
    heap_el = program_el.find_child('heap')

    if heap_el is None:
        heap_el = ParsedElement('heap')
    
    heap_ms = collect_memsection_element(heap_el, ignore_name, prog_namespace)
    pd.attach_heap(heap_ms)
    weaver.image.image.add_group(0, [heap_ms.get_ms()])
    # Fill env with default values.
    env.add_entry(key      = "HEAP",
                  cap_name = 'heap/rw',
                  attach   = heap_ms.get_attrs().attach)

    env.add_entry(key      = "HEAP_BASE",
                  base     = heap_ms)
    env.add_entry(key      = "HEAP_SIZE",
                  value    = heap_ms.get_attrs().size)

    pd.add_environment(env)

    weaver.bootinfo.bootinfo.add_pd(pd)

    weaver.image.image.pop_attrs()


def collect_pd_element(pd_el, ignore_name, namespace):
    """Collect the attributes of a PD element."""

    # New namespace for objects living in the PD.
    pd_namespace = namespace.add_namespace(pd_el.name)

    pd = weaver.bootinfo.PD(pd_el.name, pd_namespace)

    virtpool = getattr(pd_el, 'virtpool', None)
    physpool = getattr(pd_el, 'physpool', None)
    direct   = getattr(pd_el, 'direct',   None)

    if hasattr(pd_el, 'pager'):
        pager = make_pager_attr(pd_el.pager)
    else:
        pager = None

    # Push the overriding attributes for the pd.
    weaver.image.image.push_attrs(virtual  = virtpool,
                                  physical = physpool,
                                  pager    = pager,
                                  direct   = direct)

    pd.set_default_pools()

    # Collect the object environment
    env = collect_environment_element(pd_el.find_child('environment'),
                                      pd_namespace)

    if hasattr(pd_el, "file"):
        elf = ElfFile.from_filename(pd_el.file)

        if elf.get_elf_type() != ET_EXEC:
            raise MergeError, "All the merged ELF files must be of EXEC type."

        segment_els = pd_el.find_all_children("segment")
        segs = collect_elf_segments(elf,
                                    weaver.image.image.PROGRAM,
                                    segment_els,
                                    pd_el.name,
                                    pd_namespace)

        segs_ms = [weaver.bootinfo.bootinfo.record_segment_info(pd_el.name, seg) for seg in segs]
        for seg_ms in segs_ms:
            pd.attach_memsection(seg_ms)

        # Record any patches being made to the program.
        patch_els   = pd_el.find_children("patch")
        collect_patches(elf, patch_els)

    # Collect threads in the PD.
    for thread_el in pd_el.find_children('thread'):
        if not ignore_name.match(thread_el.name):
            thread = collect_thread(elf, thread_el, ignore_name,
                                    pd_namespace,
                                    entry = "_thread_start")
            pd.add_thread(thread)

            # Record the thread and its stack in the environment.
            env.add_entry(key      = thread.get_name(),
                          cap_name = thread.get_name() + '/master')
            env.add_entry(key      = thread.get_name() + "/STACK",
                          cap_name = thread.get_name() + '/stack/rw',
                          attach   = thread.get_stack().get_attrs().attach)

    # Collect memsections in the PD.
    for ms_el in pd_el.find_children('memsection'):
        if not ignore_name.match(ms_el.name):
            ms = collect_memsection_element(ms_el, ignore_name, pd_namespace)
            pd.attach_memsection(ms)

            weaver.image.image.add_group(0, [ms.get_ms()])

            env.add_entry(key      = ms.get_name(),
                          cap_name = ms.get_name() + '/master',
                          attach   = ms.get_attrs().attach)

    # Collect any zones in the program.
    for zone_el in pd_el.find_children('zone'):
        (zone, non_zone_ms) = \
               collect_zone_element(zone_el, ignore_name, pd_namespace)

        pd.attach_zone(zone)

        # Attach zones that aren't part of the zone to the program.
        for ms in non_zone_ms:
            pd.attach_memsection(ms)

            weaver.image.image.add_group(0, [ms.get_ms()])

            env.add_entry(key      = ms.get_name(),
                          cap_name = ms.get_name() + '/master',
                          attach   = ms.get_attrs().attach)

    pd.add_environment(env)

    weaver.bootinfo.bootinfo.add_pd(pd)
    weaver.image.image.pop_attrs()

def collect_program_pd_elements(parsed, ignore_name, namespace):
    """Go through and handle all the iguana program objects that we have."""

    for el in parsed.children:
        if el.tag == "program":
            if not ignore_name.match(el.name):
                collect_program_element(el, ignore_name, namespace)
        elif el.tag == "pd":
            if not ignore_name.match(el.name):
                collect_pd_element(el, ignore_name, namespace)

