#!/usr/bin/perl
#     (c)2003-2011 Broadcom Corporation
#
#  This program is the proprietary software of Broadcom Corporation and/or its licensors,
#  and may only be used, duplicated, modified or distributed pursuant to the terms and
#  conditions of a separate, written license agreement executed between you and Broadcom
#  (an "Authorized License").  Except as set forth in an Authorized License, Broadcom grants
#  no license (express or implied), right to use, or waiver of any kind with respect to the
#  Software, and Broadcom expressly reserves all rights in and to the Software and all
#  intellectual property rights therein.  IF YOU HAVE NO AUTHORIZED LICENSE, THEN YOU
#  HAVE NO RIGHT TO USE THIS SOFTWARE IN ANY WAY, AND SHOULD IMMEDIATELY
#  NOTIFY BROADCOM AND DISCONTINUE ALL USE OF THE SOFTWARE.
#
#  Except as expressly set forth in the Authorized License,
#
#  1.     This program, including its structure, sequence and organization, constitutes the valuable trade
#  secrets of Broadcom, and you shall use all reasonable efforts to protect the confidentiality thereof,
#  and to use this information only in connection with your use of Broadcom integrated circuit products.
#
#  2.     TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS"
#  AND WITH ALL FAULTS AND BROADCOM MAKES NO PROMISES, REPRESENTATIONS OR
#  WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO
#  THE SOFTWARE.  BROADCOM SPECIFICALLY DISCLAIMS ANY AND ALL IMPLIED WARRANTIES
#  OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE,
#  LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION
#  OR CORRESPONDENCE TO DESCRIPTION. YOU ASSUME THE ENTIRE RISK ARISING OUT OF
#  USE OR PERFORMANCE OF THE SOFTWARE.
#
#  3.     TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL BROADCOM OR ITS
#  LICENSORS BE LIABLE FOR (i) CONSEQUENTIAL, INCIDENTAL, SPECIAL, INDIRECT, OR
#  EXEMPLARY DAMAGES WHATSOEVER ARISING OUT OF OR IN ANY WAY RELATING TO YOUR
#  USE OF OR INABILITY TO USE THE SOFTWARE EVEN IF BROADCOM HAS BEEN ADVISED OF
#  THE POSSIBILITY OF SUCH DAMAGES; OR (ii) ANY AMOUNT IN EXCESS OF THE AMOUNT
#  ACTUALLY PAID FOR THE SOFTWARE ITSELF OR U.S. $1, WHICHEVER IS GREATER. THESE
#  LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF
#  ANY LIMITED REMEDY.
#
# $brcm_Workfile: bapi_classes.pm $
# $brcm_Revision: 1 $
# $brcm_Date: 12/7/11 10:41a $
#
# File Description:
#
# Revision History:
#
# $brcm_Log: /nexus/build/tools/common/bapi_classes.pm $
# 
# 1   12/7/11 10:41a erickson
# SW7420-2141: merge as much duplicated kernelmode proxy/usermode ipc
#  perl code as possible
# 
#############################################################################
use strict;

package bapi_classes;

# generate hash of destructor functions mapped to their various destructors by attr
# the key is the constructor
# one destructor per attr per function
# the validation of destructors happens only here. every one else trusts this output.
sub get_destructors
{
    my ($funcs) = @_;
    my $func;
    my ($attr,$value);
    my %destructors;

    for $func (@$funcs) {
        if(exists $func->{ATTR}) {
            while (($attr, $value) = each %{$func->{ATTR}} ) {
                # one "if" clause for each prototype of destructor. valid prototypes are:
                # 1) void destructor(resource_handle)
                # 2) void destructor(main_handle, resource_handle)
                if($attr eq 'destructor' || $attr eq 'release' || $attr eq 'dealloc') {
                    my $destructor;
                    for (@$funcs) {
                        if ($value eq $_->{FUNCNAME}) {
                            $destructor = $_;
                            last;
                        }
                    }
                    if ($attr eq 'dealloc') {
                        # dealloc destructor param type is not class type
                        if (defined $destructor && $destructor->{RETTYPE} eq 'void' &&
                            (scalar @{$destructor->{PARAMS}}) == 1 )
                        {
                            $destructors{$func}->{$attr} = $destructor;
                            next;
                        }
                    }
                    else {
                       if (defined $destructor && $destructor->{RETTYPE} eq 'void' &&
                           (scalar @{$destructor->{PARAMS}}) == 1 &&
                           ${$destructor->{PARAMS}}[0]->{TYPE} eq $func->{RETTYPE} )
                        {
                            $destructors{$func}->{$attr} = $destructor;
                            next;
                        }
                    }
                    print STDERR "ERROR: invalid $attr $value in function $func->{FUNCNAME}\n";
                }
                elsif($attr eq 'secondary_destructor') {
                    my $destructor;
                    for (@$funcs) {
                        if ($value eq $_->{FUNCNAME}) {
                            $destructor = $_;
                            last;
                        }
                    }
                    if (defined $destructor && $destructor->{RETTYPE} eq 'NEXUS_Error' &&
                        (scalar @{$destructor->{PARAMS}}) == 2 &&
                        ${$destructor->{PARAMS}}[0]->{TYPE} eq ${$func->{PARAMS}}[0]->{TYPE} &&
                        ${$destructor->{PARAMS}}[1]->{TYPE} eq $func->{RETTYPE} )
                    {
                        # TODO: this only allows one type of secondary destructor. may need to expand this.
                        $destructors{$func}->{$attr} = $destructor;
                        next;
                    }
                    print STDERR "ERROR: invalid $attr $value in function $func->{FUNCNAME}\n";
                }
            }
        }
    }
    return \%destructors;
}

# check if a function is a destructor and which handle is being destroyed
sub get_stopcallbacks_handle
{
    my ($func, $destructors) = @_;
    my $d;

    for $d (keys %{$destructors}) {
        if ($destructors->{$d}->{'destructor'} == $func) {
            return $func->{PARAMS}[0]->{NAME};
        }
    }
    for $d (keys %{$destructors}) {
        if ($destructors->{$d}->{'secondary_destructor'} == $func) {
            return $func->{PARAMS}[1]->{NAME};
        }
    }
    return undef;
}

# return a list of classes
sub get_classes
{
    my ($funcs, $destructors) = @_;
    my $func;
    my %classes;
    my $class;

    for $func (@$funcs) {
        my $attr;
        foreach $attr (keys %{$destructors->{$func}}) {
            my $destructor = $destructors->{$func}->{$attr};

            #print "$func->{FUNCNAME} $attr -> $destructor->{FUNCNAME}\n";
            if ($attr eq "release") {
                my $class_type = $func->{RETTYPE};
                $class = $classes{$class_type};

                if(defined $class && defined $class->{RELEASE}) {
                    print STDERR "ERROR: cannot have more than one acquire/release acquire=$func->{FUNCNAME}, release=$destructor->{FUNCNAME}\n";
                }
                else {
                    $class->{RELEASE} = $destructor;
                    $class->{ACQUIRE} = $func;
                    $classes{$class_type} = $class;
                }
            }
            elsif ($attr eq "secondary_destructor") {
                my $class_type = $func->{RETTYPE};
                $class = $classes{$class_type};
                push @{$class->{SECONDARY_DESTRUCTORS}}, $destructor;
                # secondary_destructor must follow primary destructor
                $classes{$class_type} = $class;
            }
            else {
                my $class_type = $func->{RETTYPE};

                if ($attr eq "dealloc") {
                    # dealloc doesn't have handle. use destructor func name instead
                    $class_type = $destructor->{FUNCNAME};
                }

                $class = $classes{$class_type};

                if(defined $class && $class->{DESTRUCTOR}) {
                    # you can have more than one constructor per class, but only one destructor per class
                    if($class->{DESTRUCTOR} == $destructor) {
                        push @{$class->{CONSTRUCTORS}}, $func;
                    } else {
                        print STDERR "ERROR: destructor mismatch $destructor->{FUNCNAME} for function $func->{FUNCNAME}:$func->{RETTYPE}\n";
                        print STDERR "  $class->{DESTRUCTOR} != $destructor\n";
                    }
                } else {
                    $class->{DESTRUCTOR} = $destructor;
                    $class->{CONSTRUCTORS} = [$func];
                    $class->{DESTRUCTOR_TYPE} = $attr;
                    $class->{CLASS_TYPE} = $class_type;
                    $classes{$class_type} = $class;
                }
            }
        }
        if (exists $func->{ATTR}->{"shutdown"}) {
            my $class_type = $func->{PARAMS}[0]->{TYPE};
            $class = $classes{$class_type};
            my $shutdown;
            $shutdown->{'shutdown_target'} = $func->{ATTR}->{'shutdown'};
            $shutdown->{'shutdown_get_connector'} = $func->{FUNCNAME};
            push @{$class->{SHUTDOWN}}, $shutdown;
            $classes{$class_type} = $class;
        }
        
    }

    my @c = values %classes;
    return \@c;
}

sub callable_by_untrusted
{
    my ($func, $classes, $untrusted_api) = @_;

    my $allow_funcs = $untrusted_api->{'allow_funcs'};
    my $deny_funcs = $untrusted_api->{'deny_funcs'};
    my $params = $func->{PARAMS};

    if (grep {$_ eq $func->{FUNCNAME}} @$allow_funcs) {
        return 1;
    }

    if (grep {$_ eq $func->{FUNCNAME}} @$deny_funcs) {
        return 0;
    }

    if ((scalar @{$params}) == 1 && !${$params}[0]->{INPARAM}) {
        # GetDefaultSettings allowed
        return 1;
    }

    # first param is validated handle
    if ((scalar @{$params}) >= 1) {
        if (${$params}[0]->{TYPE} eq 'NEXUS_HeapHandle') {
            return 1;
        }
        for (@$classes) {
            my $class = $_;
            my $handletype = $class->{CLASS_TYPE};
            if (${$params}[0]->{TYPE} eq $handletype) {
                return 1;
            }
        }
    }

    # untrusted
    return 0;
}

sub skip_thunk
{
    my $file = shift;
    return $file =~ /_init.h$/ ||
        $file =~ /nexus_platform_client.h$/ ||
        $file =~ /nexus_base\w*.h$/;
}

# generate global functions which verify handles per module
# this prevents the need for a global class database. $class_id can remain local.
sub generate_class_verify_functions
{
    my ($file, $classes, $module) = @_;
    my $class_id = 0;
    $module = lc $module;
    for (@$classes) {
        my $class = $_;
        my $handletype = $_->{CLASS_TYPE};

        # required for cross-module handle verification
        print $file "int nexus_driver_verify_$handletype(void *client_id, void *object) {\n";
        print $file "  return b_objdb_verify_locked(&nexus_driver_module_state.header.objdb.class_table[$class_id], object, client_id);\n";
        print $file "}\n";

        # these functions can be called from inside nexus modules
        print $file "/* adds/removes handle from objdb */\n";
        print $file "int nexus_register_${handletype}(void *client, void *handle, bool acquire) {\n";
        print $file "  NEXUS_ASSERT_MODULE();\n";
        print $file "  return b_objdb_insert_locked(&nexus_driver_module_state.header.objdb.class_table[$class_id], handle, client, acquire);\n";
        print $file "}\n";
        print $file "void nexus_unregister_${handletype}(void *client, void *handle, bool release) {\n";
        # can't NEXUS_ASSERT_MODULE because we could be in module uninit
        print $file "  b_objdb_remove_locked(&nexus_driver_module_state.header.objdb.class_table[$class_id], handle, client, release);\n";
        print $file "}\n";
        ++$class_id;
    }
    print $file "/* returns the client current inside this module */\n";
    print $file "void *nexus_${module}_client(void) {\n";
    print $file "  NEXUS_ASSERT_MODULE();\n";
    print $file "  return (nexus_driver_module_state.header.current_client?nexus_driver_module_state.header.current_client:&nexus_driver_get_server()->client);\n";
    print $file "}\n";
}

sub generate_class_table
{
    my ($file, $classes)=@_;
    if(scalar @$classes) {
        my $class;
                
        # create local close function so that pre-close functions can be called
        for $class (@$classes) {
            next if ($class->{DESTRUCTOR_TYPE} ne 'destructor');
            next if (!exists $class->{SHUTDOWN});
            
            my $handletype = $class->{CLASS_TYPE};
            my $func = $class->{DESTRUCTOR};
            
            # shutdown_close will perform a platform-level shutdown of the connector, then close.
            print $file "static void nexus_driver_shutdown_close_$handletype($handletype handle)\n";
            print $file "{\n";
            print $file "  NEXUS_UnlockModule();\n";
            for (@{$class->{SHUTDOWN}}) {
                my $shutdown_target = $_->{'shutdown_target'};
                my $shutdown_get_connector = $_->{'shutdown_get_connector'};
                my $params = $func->{PARAMS};
                print $file "  nexus_driver_shutdown_$shutdown_target($shutdown_get_connector(handle));\n";
            }
            print $file "  NEXUS_LockModule();\n";
            # actual close
            print $file "  $func->{FUNCNAME}(handle);\n";
            print $file "}\n\n";
            
        }
        
        print $file "B_OBJDB_TABLE_BEGIN(NEXUS_DRIVER_MODULE_CLASS_TABLE)\n";
        for $class (@$classes) {
            my $handletype = $class->{CLASS_TYPE};
            my $destructorfunc;
            if ($class->{DESTRUCTOR_TYPE} eq 'destructor' && exists $class->{SHUTDOWN}) {
                $destructorfunc = "nexus_driver_shutdown_close_$handletype";
            }
            else {
                $destructorfunc = $class->{DESTRUCTOR}->{FUNCNAME};
            }
            my $releasefunc = $class->{RELEASE}->{FUNCNAME};
            if (!defined $destructorfunc) {
                $destructorfunc = "ERROR: missing destructor"; # required. this will cause the compilation to fail.
            }
            if (!defined $releasefunc) {
                $releasefunc = "NULL"; # not required
            }
            print $file "    B_OBJDB_TABLE_ENTRY($handletype,$releasefunc,$destructorfunc)\n";
        }
        print $file "B_OBJDB_TABLE_END\n";
    }
}

1;
