# t32_multi.py
#
# Example Python script that shows how to launch 2 instances of TRACE32.
# The script will make use of the Channel functions to manage both
# instances. this is useful in multi-core debug environments.
# Requires:
#     Python 3
#     Tkinter


from tkinter import *
from tkinter import messagebox
#import sys
import platform
import time

# Import our helper class
from oshelper import OSHelper

import ctypes        # module for C data types
import enum
# We need void* to pass to some of the T32* functions
from ctypes import c_void_p


T32_DEV = 1
connstate=FALSE

# These are UDP port numbers and must be available on your
# host PC. Change them if required.
T32_APIPORT1    = 20001
T32_APIPORT2    = 20002
T32_ICPORT1     = T32_APIPORT1+10
T32_ICPORT2     = T32_APIPORT2+10

# Use this class to determine the state of any scripts that we get
# TRACE32 to run via the API.
class PracticeState(enum.IntEnum):
    UNKNOWN = -1
    NOT_RUNNING = 0
    RUNNING = 1
    DIALOG_OPEN = 2

class Trace32State(enum.IntEnum):
    UNKNOWN = -1
    DOWN = 0
    NOACCESS = 1
    HALTED = 2
    RUNNING = 3



# Our simple GUI is defined in this class
class simple_tk_gui:
    def __init__(self, parent):
        self.connstate = FALSE
        self.parent = parent
        self.parent.minsize(width=350, height = 200)
        parent.title("TRACE32 Multi-core Example")

        self.topframe = Frame(root)
        self.topframe.pack(fill=BOTH, padx=5, pady=5)

        self.midframe = Frame(root)
        self.midframe.pack(fill=BOTH, padx = 5, pady = 5)

        self.bottomframe = Frame(root)
        self.bottomframe.pack(side=TOP, padx = 5, pady = 5 )

        self.results = Listbox(self.midframe, bg="WHITE", width=40)
        self.lbscroll = Scrollbar(self.midframe, orient=VERTICAL)
        self.lbscroll.pack(side=RIGHT,fill=Y)
        self.results.config(yscrollcommand=self.lbscroll.set)
        self.lbscroll.config(command=self.results.yview)
        self.results.pack(side=RIGHT, pady=5, padx=5)

        txt="Start {0}".format(int(T32_APIPORT2))
        self.startslave_button = Button(self.bottomframe,
                                         text=txt,
                                         command=self.start_slave)
        self.startslave_button.pack(side=RIGHT, padx=5, pady=5)

        txt="Start {0}".format(int(T32_APIPORT1))
        self.startmaster_button = Button(self.bottomframe,
                                         text=txt,
                                         command=self.start_master)
        self.startmaster_button.pack(side=LEFT, padx=5, pady=5)

        txt="Synch ('{0}')  ('{1}')\nStart  <---> Start\n".format(
            int(T32_APIPORT1), int(T32_APIPORT2))
        txt+="Stop  <--->Stop\nStep  <--->Step"
        self.master1_button = Button(self.topframe,
                                         text=txt,
                                         command=self.setsynchall)
        self.master1_button.pack(side=LEFT, padx=5)

        txt="Synch ('{0}')  ('{1}')\nStart   ---> Start\n".format(
            int(T32_APIPORT1), int(T32_APIPORT2))
        txt+="Stop  <--->Stop\nStep   -X- Step"
        self.master2_button = Button(self.topframe,
                                         text=txt,
                                         command=self.synchstartstop)
        self.master2_button.pack(side=RIGHT, padx=5)

        txt="Synch ('{0}')  ('{1}')\nStart   ---> Start\n".format(
            int(T32_APIPORT1), int(T32_APIPORT2))
        txt+="Stop   --->Stop\nStep   ---> Step"
        self.master3_button = Button(self.topframe,
                                         text=txt,
                                         command=self.synchoneway)
        self.master3_button.pack(side=RIGHT, padx=5)

        txt="Step {0}".format(int(T32_APIPORT2))
        self.stepslave_button = Button(self.bottomframe,
                                         text=txt,
                                         command=self.stepslave)
        self.stepslave_button.pack(side=RIGHT, padx=5)

        txt="Step {0}".format(int(T32_APIPORT1))
        self.stepmaster_button = Button(self.bottomframe,
                                         text=txt,
                                         command=self.stepmaster)
        self.stepmaster_button.pack(side=RIGHT, padx=5)

    def initialise_synch(self):
        # Set the initial synchronisation configuration.
        # For both instances of TRACE32:
        #    Open the SYNCH window
        #    Reset any previous settings
        t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
        t32api.T32_Cmd(b'SYNCH.Reset')
        t32api.T32_Cmd(b'SYNCH')
        txt="SYNCH.CONNECT localhost:%d" % T32_ICPORT2
        t32api.T32_Cmd(txt.encode())
        t32api.T32_Cmd(b'SYNCH.ON')

        # switch to a different instance and issue the reset commands
        t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
        t32api.T32_Cmd(b'SYNCH.Reset')
        t32api.T32_Cmd(b'SYNCH')
        txt="SYNCH.CONNECT localhost:%d" % T32_ICPORT1
        t32api.T32_Cmd(txt.encode())
        t32api.T32_Cmd(b'SYNCH.ON')

    def stepmaster(self):
        # Select Channel 1 and issue a command to step 1 HLL instruction
        t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
        t32api.T32_Cmd(b"STEP.HLL")
        txt="Stepping TRACE32 '{0}".format(int(T32_APIPORT1))
        item=txt
        self.results.insert(END,item)
        self.parent.update()
        pass

    def stepslave(self):
        # Select Channel 2 and issue a command to step 1 HLL instruction
        t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
        t32api.T32_Cmd(b"STEP.HLL")
        txt="Stepping TRACE32 '{0}".format(int(T32_APIPORT2))
        item=txt
        self.results.insert(END,item)
        self.parent.update()
        pass

    def synchstartstop(self):
        # Reset any synch settings
        self.initialise_synch()

        t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
        t32api.T32_Cmd(b"SYNCH")
        t32api.T32_Cmd(b"SYNCH.MASTERGO ON")
        t32api.T32_Cmd(b"SYNCH.MASTERBRK ON")
        t32api.T32_Cmd(b"SYNCH.MASTERSTEP OFF")
        t32api.T32_Cmd(b"SYNCH.SLAVEGO OFF")
        t32api.T32_Cmd(b"SYNCH.SLAVEBRK ON")
        t32api.T32_Cmd(b"SYNCH.SLAVESTEP OFF")

        # Switch to the other instance and set the values there too
        t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
        t32api.T32_Cmd(b"SYNCH")
        t32api.T32_Cmd(b"SYNCH.MASTERGO OFF")
        t32api.T32_Cmd(b"SYNCH.MASTERBRK ON")
        t32api.T32_Cmd(b"SYNCH.MASTERSTEP OFF")
        t32api.T32_Cmd(b"SYNCH.SLAVEGO ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEBRK ON")
        t32api.T32_Cmd(b"SYNCH.SLAVESTEP OFF")


    def synchoneway(self):
        self.initialise_synch()

        t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
        t32api.T32_Cmd(b"SYNCH")
        t32api.T32_Cmd(b"SYNCH.MASTERGO ON")
        t32api.T32_Cmd(b"SYNCH.MASTERBRK ON")
        t32api.T32_Cmd(b"SYNCH.MASTERSTEP ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEGO OFF")
        t32api.T32_Cmd(b"SYNCH.SLAVEBRK OFF")
        t32api.T32_Cmd(b"SYNCH.SLAVESTEP OFF")

        # Switch to the other instance and set the values there too
        t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
        t32api.T32_Cmd(b"SYNCH")
        t32api.T32_Cmd(b"SYNCH.MASTERGO OFF")
        t32api.T32_Cmd(b"SYNCH.MASTERBRK OFF")
        t32api.T32_Cmd(b"SYNCH.MASTERSTEP OFF")
        t32api.T32_Cmd(b"SYNCH.SLAVEGO ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEBRK ON")
        t32api.T32_Cmd(b"SYNCH.SLAVESTEP ON")



    def start_slave(self):
        # toggle the button text and function.
        # Add a line of text to the listbox to indicate what we've just done.
        if "Start" in self.startslave_button['text']:
            txt="Stop {0}".format(int(T32_APIPORT2))
            self.startslave_button.config(text=txt)
            t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
            t32api.T32_Cmd(b'GO')
            item="Starting TRACE32 {0}".format(int(T32_APIPORT2))
            self.results.insert(END,item)
            self.parent.update()
        else:
            txt="Start {0}".format(int(T32_APIPORT2))
            self.startslave_button.config(text=txt)
            t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
            t32api.T32_Cmd(b"BREAK")
            item="Stopping TRACE32 {0}".format(int(T32_APIPORT2))
            self.results.insert(END,item)
            self.parent.update()

    def start_master(self):
        # toggle the button text and function.
        # Add a line of text to the listbox to indicate what we've just done.
        if "Start" in self.startmaster_button['text']:
            txt="Stop {0}".format(int(T32_APIPORT1))
            self.startmaster_button.config(text=txt)
            t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
            t32api.T32_Cmd(b'GO')
            item="Starting TRACE32 {0}".format(int(T32_APIPORT1))
            self.results.insert(END,item)
            self.parent.update()
        else:
            txt="Start {0}".format(int(T32_APIPORT1))
            self.startmaster_button.config(text=txt)
            t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
            t32api.T32_Cmd(b"BREAK")
            item="Stopping TRACE32 {0}".format(int(T32_APIPORT1))
            self.results.insert(END,item)
            self.parent.update()


    def setsynchall(self):
        # Set All synch options to allow for:
        # Master start - Slave start
        # Master stop - slave stop
        # Master step - Slave Step
        # Slave Start/Stop/Step - Master Start/Stop/Step
        self.initialise_synch()

        t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
        t32api.T32_Cmd(b"SYNCH")
        t32api.T32_Cmd(b"SYNCH.MASTERGO ON")
        t32api.T32_Cmd(b"SYNCH.MASTERBRK ON")
        t32api.T32_Cmd(b"SYNCH.MASTERSTEP ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEGO ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEBRK ON")
        t32api.T32_Cmd(b"SYNCH.SLAVESTEP ON")

        # Switch to the other instance and set the values there too
        t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
        t32api.T32_Cmd(b"SYNCH")
        t32api.T32_Cmd(b"SYNCH.MASTERGO ON")
        t32api.T32_Cmd(b"SYNCH.MASTERBRK ON")
        t32api.T32_Cmd(b"SYNCH.MASTERSTEP ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEGO ON")
        t32api.T32_Cmd(b"SYNCH.SLAVEBRK ON")
        t32api.T32_Cmd(b"SYNCH.SLAVESTEP ON")


def closeWindow():
    result=messagebox.askyesno("Exit?",
                               "Quitting will also close TRACE32.\nAre you sure?")
    if result==FALSE:
        return
    if connstate==TRUE:
        # This will instruct TRACE32 to close the session and terminate
        # QT based TRACE32 running on Mac OS and Linux do not close
        # correctly when calling T32_Terminate(). This workaround solves that.
        # Use T32_SetChannel to ensure both instances of TRACE32 are terminated
        if platform.system() == "Windows":
            t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
            t32api.T32_Terminate(0)
            t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
            t32api.T32_Terminate(0)
        else:
            t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
            t32api.T32_Cmd(b'quit')
            t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
            t32api.T32_Cmd(b'quit')
    else:
        t32process1.kill()
        t32process2.kill()
    root.destroy()

# Function for connecting to an instance of TRACE32
def connect_T32(APIPORT):
    port="%d" % (APIPORT)
    t32api.T32_Config(b"NODE=",b"localhost")
    t32api.T32_Config(b"PORT=",port.encode())
    t32api.T32_Config(b"PACKLEN=",b"1024")

    # Establish a connection to TRACE32
    rc = t32api.T32_Init()
    if rc != 0:
        connstate=FALSE
        print("Error with T32_Init")

    # Sometimes the first attempt to Attach fails but a second will
    # usually succeed. This often happens if the API has been left hanging
    # when the previous user didn't call T32_Exit() before quitting.
    for x in range(3):
        rc = t32api.T32_Attach(T32_DEV)
        if rc == 0:
            break
    if rc != 0:
        connstate=FALSE
        t32api.T32_Exit()
        print("Error in T32_Attach")



def waitforscript():
    # This function will loop, checking whether the script we just
    # launched has completed or not. Control will not pass back to us until
    # it has finished.
    state = ctypes.c_int(PracticeState.UNKNOWN)
    rc = 0
    while rc==0 and not state.value==PracticeState.NOT_RUNNING:
        time.sleep(0.05)            # 50 ms pause in each loop
        rc = t32api.T32_GetPracticeState(ctypes.byref(state))


# Main program loop starts here

# Create an OS Helper object
osh=OSHelper()
# Load the TRACE32 API shared library
apifile=osh.get_apifile()
if apifile==-1:
    print("Error with API file {}\nQuitting".format(osh.get_apifile()))
    quit()
t32api=ctypes.cdll.LoadLibrary(osh.get_apifile())

# Launch an instance of TRACE32 using the helper class
osh.set_apiport(T32_APIPORT1)
osh.set_icport(T32_ICPORT1)
osh.set_connectiontype("SIM")
t32process1=osh.launch_trace32("t32mppc.exe")

# Create a channel to an instance of TRACE32.
# This is essentially a local structure to hold the context info for an instance
# Holding multiple context structures like this allows us to switch the API
# from one instance to another.

# Get the size of the data to be held
rc = t32api.T32_GetChannelSize()
# This allows us to hold a Python object that also doubles as a C void* to
# get the channel data
ch1 = ctypes.create_string_buffer(rc)
t32api.T32_GetChannelDefaults(ctypes.cast(ch1,ctypes.c_void_p))


# Repeat the process to launch a second instance of TRACE32 and create a channel
# for the API
osh.set_apiport(T32_APIPORT2)
osh.set_icport(T32_ICPORT2)
osh.set_connectiontype("SIM")
t32process2=osh.launch_trace32("t32mppc.exe")
ch2 = ctypes.create_string_buffer(rc)
t32api.T32_GetChannelDefaults(ctypes.cast(ch2,ctypes.c_void_p))

# Arrange TRACE32 GUIS side by side, load  demo program and set
# each instance to main()

# To control instance 1, set the Channel and issue the commands
t32api.T32_SetChannel(ctypes.cast(ch1,c_void_p))
connect_T32(T32_APIPORT1)
cmd='DO %s/demo/powerpc/compiler/gnu/demo_sram.cmm' % osh.get_sysdir()
t32api.T32_Cmd(cmd.encode())
# Wait for the script to complete
waitforscript()
# Set main window size
t32api.T32_Cmd(b"FRAMEPOS 0% 0% 48% 85%")
# Run until PC reaches main()
t32api.T32_Cmd(b'go main')

# To control instance 2, set the channel and issue some commands
t32api.T32_SetChannel(ctypes.cast(ch2,c_void_p))
connect_T32(T32_APIPORT2)
cmd='DO %s/demo/powerpc/compiler/gnu/demo_sram.cmm' % osh.get_sysdir()
t32api.T32_Cmd(cmd.encode())
# Wait for the script to complete
waitforscript()
# Set window size
t32api.T32_Cmd(b"FRAMEPOS 50% 0% 48% 85%")
# Run until PC reaches main()
t32api.T32_Cmd(b'go main')


# Main GUI loop starts here
root = Tk()
my_gui = simple_tk_gui(root)

# Catch the window close event and provide our own handler
root.protocol("WM_DELETE_WINDOW",closeWindow)
root.mainloop()
