2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org
.jpc
.plugins
;
37 import java
.awt
.event
.*;
39 import java
.lang
.reflect
.*;
42 import org
.jpc
.emulator
.PC
;
43 import org
.jpc
.emulator
.HardwareComponent
;
44 import org
.jpc
.emulator
.VGADigitalOut
;
45 import org
.jpc
.pluginsbase
.Plugins
;
46 import org
.jpc
.pluginsbase
.Plugin
;
47 import static org
.jpc
.Misc
.parseStringToComponents
;
48 import static org
.jpc
.Misc
.errorDialog
;
49 import static org
.jpc
.Misc
.moveWindow
;
50 import static org
.jpc
.Misc
.openStream
;
52 //Locking this class is used for preventing termination and when terminating.
53 public class LuaPlugin
implements ActionListener
, Plugin
55 private JFrame window
;
57 private Plugins vPluginManager
;
58 private String kernelName
;
59 private Map
<String
, String
> kernelArguments
;
60 private Map
<String
, String
> userArguments
;
61 private int nativeWidth
;
62 private int nativeHeight
;
63 private JLabel execLabel
;
64 private JTextField execName
;
65 private JButton execButton
;
66 private JButton termButton
;
67 private JButton clearButton
;
68 private JTextArea console
;
70 private int nextHandle
;
72 //luaThread is null if Lua isn't running.
73 private Thread luaThread
;
75 private volatile boolean pcRunning
;
76 private volatile String luaInvokeReq
;
77 private volatile boolean luaTerminateReq
;
78 private volatile boolean luaTerminateReqAsync
;
79 private VGADigitalOut screenOut
;
81 private volatile boolean ownsVGALock
;
82 private volatile boolean ownsVGALine
;
83 private volatile boolean signalComplete
;
84 private volatile boolean luaStarted
;
85 private volatile boolean mainThreadWait
;
86 private volatile boolean reconnectInProgress
;
88 private VGARetraceWaiter vgaPoller
;
90 private boolean consoleMode
;
91 private boolean specialNoGUIMode
;
93 private Map
<String
, LuaResource
> resources
;
94 private IdentityHashMap
<LuaResource
, Integer
> liveObjects
;
96 public static abstract class LuaResource
101 public LuaResource(LuaPlugin _plugin
)
103 handle
= "h" + (_plugin
.nextHandle
++);
105 plugin
.resources
.put(handle
, this);
108 public final String
getHandle()
113 public final void release(boolean noExceptions
) throws IOException
117 } catch(IOException e
) {
121 plugin
.liveObjects
.remove(this);
122 plugin
.resources
.remove(handle
);
125 public abstract void destroy() throws IOException
;
128 private String
getMethodHandle(Lua l
)
130 if(l
.type(1) == Lua
.TNONE
) {
131 l
.error("Handle required for method call");
134 l
.checkType(1, Lua
.TUSERDATA
);
135 Object _u
= l
.toUserdata(l
.value(1)).getUserdata();
136 if(!(_u
instanceof String
)) {
138 l
.error("Invalid handle to resource: " + _u
.getClass().getName());
140 l
.error("Invalid handle to resource: Null");
147 public void destroyLuaObject(Lua l
) throws IOException
149 String u
= getMethodHandle(l
);
150 LuaResource r1
= resources
.get(u
);
152 l
.error("Bad or closed handle passed to method");
157 public boolean systemShutdown()
159 //Just terminate the emulator.
163 public void reconnect(PC _pc
)
165 vgaPoller
.deactivate();
166 //Get the event waiter out of way.
167 reconnectInProgress
= true;
168 if(luaThread
!= null)
169 luaThread
.interrupt();
171 reconnectInProgress
= false;
172 if(ownsVGALock
&& screenOut
!= null) {
173 screenOut
.releaseOutput(this);
176 if(ownsVGALine
&& screenOut
!= null) {
177 screenOut
.unsubscribeOutput(this);
181 screenOut
= _pc
.getVideoOutput();
187 if(screenOut
!= null && luaThread
!= null) {
188 screenOut
.subscribeOutput(this);
190 vgaPoller
.reactivate();
193 queueEvent("attach", null);
196 public void pcStarting()
201 public void pcStopping()
204 queueEvent("stop", null);
207 class LuaCallback
extends LuaJavaCallback
209 Method callbackMethod
;
212 LuaCallback(Object target
, Method callback
)
215 callbackMethod
= callback
;
218 public int luaFunction(Lua l
) {
219 synchronized(LuaPlugin
.this) {
221 if(!liveObjects
.containsKey(onObject
)) {
222 l
.error("Attempted to call method on dead object");
225 return ((Integer
)callbackMethod
.invoke(onObject
, luaState
, LuaPlugin
.this)).intValue();
226 } catch(InvocationTargetException e
) {
227 if(e
.getCause() instanceof LuaError
)
228 throw (LuaError
)e
.getCause(); //Pass runtime exceptions through.
229 errorDialog(e
.getCause(), "Error in callback", null, "Terminate Lua VM");
230 terminateLuaVMAsync();
231 } catch(Exception e
) {
232 if(e
instanceof LuaError
)
233 throw (LuaError
)e
; //Pass runtime exceptions through.
234 errorDialog(e
, "Error invoking callback", null, "Terminate Lua VM");
235 terminateLuaVMAsync();
242 public void tableAddFunctions(Lua l
, LuaTable table
, Object obj
, Class
<?
> clazz
)
245 clazz
= obj
.getClass();
246 //Add all exported callbacks.
247 Method
[] candidateMethods
= clazz
.getMethods();
248 for(Method candidate
: candidateMethods
) {
249 if(obj
!= null && Modifier
.isStatic(candidate
.getModifiers()))
250 continue; //Want non-static.
251 if(obj
== null && !Modifier
.isStatic(candidate
.getModifiers()))
252 continue; //Want static.
253 if(!Modifier
.isPublic(candidate
.getModifiers()))
254 continue; //Want public.
255 if(!candidate
.getName().startsWith("luaCB_"))
256 continue; //Not this...
257 String luaName
= candidate
.getName().substring(6);
258 Class
<?
>[] paramTypes
= candidate
.getParameterTypes();
259 Class
<?
> retType
= candidate
.getReturnType();
260 if(retType
!= int.class) {
261 System
.err
.println("Warning: Incorrect return type for " + candidate
.getName() +
262 ": " + retType
.getName() + ".");
265 if(paramTypes
== null || paramTypes
.length
!= 2) {
266 System
.err
.println("Warning: Incorrect parameter type for " + candidate
.getName() + ".");
269 if(paramTypes
[0] != Lua
.class || paramTypes
[1] != LuaPlugin
.class) {
270 System
.err
.println("Warning: Incorrect parameter type for " + candidate
.getName() + ".");
274 l
.setTable(table
, luaName
, new LuaCallback(obj
, candidate
));
278 public LuaUserdata
generateLuaClass(Lua l
, LuaResource towrap
)
280 LuaUserdata user
= new LuaUserdata(towrap
.getHandle());
281 LuaTable t
= l
.newTable();
282 tableAddFunctions(l
, t
, towrap
, null);
283 liveObjects
.put(towrap
, null);
284 l
.setTable(t
, "__index" , t
);
285 l
.setMetatable(user
, t
);
290 class LuaThread
implements Runnable
295 LuaThread(Lua _lua
, String _script
)
298 StringLib
.open(_lua
);
305 private String
describeFault(int r
)
307 if(r
== 0) return null;
308 else if(r
== Lua
.YIELD
) return "Main thread yielded.";
309 else if(r
== Lua
.ERRRUN
) return "Unprotected runtime error";
310 else if(r
== Lua
.ERRSYNTAX
) return "syntax error";
311 //else if(r == Lua.ERRMEM) return "Out of memory");
312 else if(r
== Lua
.ERRFILE
) return "I/O error loading";
313 else if(r
== Lua
.ERRERR
) return "Double fault";
314 else return "Unknown fault #" + r
;
321 lua
.setGlobal("script", script
);
323 lua
.setGlobal("args", sTable
= lua
.newTable());
324 for(Map
.Entry
<String
, String
> x
: kernelArguments
.entrySet())
325 lua
.setTable(sTable
, x
.getKey(), x
.getValue());
326 for(Map
.Entry
<String
, String
> x
: userArguments
.entrySet())
327 lua
.setTable(sTable
, "x-" + x
.getKey(), x
.getValue());
329 tableAddFunctions(lua
, lua
.getGlobals(), null, LuaPlugin
.class);
331 //Wait for lua startup to be signaled in order to avoid deadlocks.
335 } catch(Exception e
) {
338 InputStream kernel
= null;
340 kernel
= new BufferedInputStream(openStream(kernelName
, "datafiles/luakernel"));
341 int r
= lua
.load(kernel
, "Kernel");
342 String fault
= describeFault(r
);
344 throw new Exception("Kernel loading error: " + fault
);
345 r
= lua
.pcall(0, 0, null);
346 fault
= describeFault(r
);
348 throw new Exception("Kernel error: " + fault
);
349 } catch(Exception e
) {
350 printConsoleMsg("\n\nLua Error: " + e
.getMessage() + "\n" +
351 lua
.value(-1).toString() + "\n\n");
352 //e.printStackTrace();
353 errorDialog(e
, "Lua error", null, "Dismiss");
355 //Lua script quit. Terminate the VM.
356 synchronized(LuaPlugin
.this) {
357 cleanupLuaResources();
360 LuaPlugin
.this.notifyAll();
362 printConsoleMsg("Lua VM: Lua script finished.\n");
366 private void cleanupLuaResources()
368 vgaPoller
.deactivate();
370 screenOut
.releaseOutput(LuaPlugin
.this);
374 screenOut
.unsubscribeOutput(LuaPlugin
.this);
378 while(resources
.size() > 0) {
379 Map
.Entry
<String
, LuaResource
> entry
= resources
.entrySet().iterator().next();
380 String key
= entry
.getKey();
381 LuaResource obj
= entry
.getValue();
384 } catch(Exception e
) {
386 resources
.remove(key
);
387 liveObjects
.remove(obj
);
397 mainThreadWait
= true;
399 if(luaInvokeReq
== null && !luaTerminateReq
)
401 mainThreadWait
= false;
403 } catch(Exception e
) {
406 if(luaInvokeReq
!= null && luaThread
== null) {
408 eventQueue
= new LinkedList
<Event
>();
409 if(screenOut
!= null && !ownsVGALine
) {
410 screenOut
.subscribeOutput(this);
412 vgaPoller
.reactivate();
415 luaState
= new Lua();
416 luaThread
= new Thread(new LuaThread(luaState
, luaInvokeReq
), "Lua execution thread");
420 signalComplete
= true;
424 } else if(luaInvokeReq
!= null) {
425 //Invoke request with Lua running? Shouldn't happen.
426 System
.err
.println("Error: Lua invoke request with Lua running!");
428 } else if(luaTerminateReq
&& luaThread
!= null) {
429 //This is fun... Terminate Lua VM. Sychronize in order to avoid terminating VM in
430 //inapporiate place. And yes, that thread gets killed! The interrupt is to prevent
431 //or kick the object from sleeping on VGA wait.
432 luaThread
.interrupt();
435 cleanupLuaResources();
438 luaTerminateReq
= false;
439 signalComplete
= true;
441 if(luaTerminateReqAsync
)
444 printConsoleMsg("Lua VM: Lua VM terminated.\n");
445 cleanupLuaResources();
446 } else if(luaTerminateReq
) {
447 //Invoke request with Lua running? Shouldn't happen.
448 System
.err
.println("Error: Lua terminate request with Lua not running!");
449 luaTerminateReq
= false;
457 public void printConsoleMsg(String msg
)
459 final String _msg
= msg
;
461 if(consoleMode
|| specialNoGUIMode
) {
462 System
.out
.print(msg
);
466 if(!SwingUtilities
.isEventDispatchThread())
468 //Do this async to avoid deadlocks with PCRunner stop.
469 SwingUtilities
.invokeLater(new Thread() { public void run() {
470 console
.setText(console
.getText() + _msg
);
472 } catch(Exception e
) {
475 console
.setText(console
.getText() + msg
);
478 private synchronized void invokeLuaVM(String script
) throws Exception
480 if(luaThread
!= null) {
484 int split
= script
.indexOf('(');
486 userArguments
= new HashMap
<String
, String
>();
488 String arguments
= script
.substring(split
+ 1);
489 int split2
= arguments
.lastIndexOf(')');
490 if(split2
!= arguments
.length() - 1)
491 throw new Exception("Bad argument syntax");
492 arguments
= arguments
.substring(0, split2
);
493 userArguments
= parseStringToComponents(arguments
);
496 //Starting from Lua itself is not possible.
497 signalComplete
= false;
499 luaInvokeReq
= script
;
501 luaInvokeReq
= script
.substring(0, split
);
502 luaTerminateReq
= false;
504 while(!signalComplete
)
507 } catch(Exception e
) {
513 private synchronized void terminateLuaVM()
516 if(luaThread
== null)
519 //This request won't go to lua execution thread.
520 signalComplete
= false;
521 luaTerminateReq
= true;
522 luaTerminateReqAsync
= false;
524 while(luaThread
!= null)
527 } catch(Exception e
) {
533 private synchronized void terminateLuaVMAsync()
535 if(luaThread
== null)
538 //This request won't go to lua execution thread.
539 signalComplete
= false;
540 luaTerminateReq
= true;
541 luaTerminateReqAsync
= true;
545 private void setLuaButtons()
547 if(consoleMode
|| specialNoGUIMode
)
550 if(!SwingUtilities
.isEventDispatchThread())
552 SwingUtilities
.invokeAndWait(new Thread() { public void run() {
553 LuaPlugin
.this.execButton
.setText((luaThread
== null) ?
"Run" : "Send");
554 LuaPlugin
.this.termButton
.setEnabled(luaThread
!= null);
556 } catch(Exception e
) {
559 LuaPlugin
.this.execButton
.setText((luaThread
== null) ?
"Run" : "Send");
560 LuaPlugin
.this.termButton
.setEnabled(luaThread
!= null);
564 private void clearConsole()
566 if(consoleMode
|| specialNoGUIMode
)
569 if(!SwingUtilities
.isEventDispatchThread())
571 SwingUtilities
.invokeAndWait(new Thread() { public void run() {
574 } catch(Exception e
) {
581 public void actionPerformed(ActionEvent evt
)
583 if(evt
.getSource() == execButton
) {
585 if(luaThread
== null)
586 invokeLuaVM(execName
.getText());
588 postMessage(execName
.getText());
589 } catch(Exception e
) {
590 printConsoleMsg("Lua script starting / message send error: " + e
.getMessage());
592 } else if(evt
.getSource() == termButton
) {
593 luaTerminateReq
= true;
594 if(luaThread
!= null)
595 luaThread
.interrupt();
597 } else if(evt
.getSource() == clearButton
) {
602 public void eci_luaplugin_sendmessage(String x
)
604 if(luaThread
!= null && x
!= null)
608 public void eci_luaplugin_setwinpos(Integer x
, Integer y
)
610 moveWindow(window
, x
.intValue(), y
.intValue(), nativeWidth
, nativeHeight
);
613 public void eci_luaplugin_run(String script
)
615 if(luaThread
== null)
618 } catch(Exception e
) {
619 printConsoleMsg("Lua script starting error: " + e
.getMessage());
623 public void eci_luaplugin_terminate()
625 luaTerminateReq
= true;
626 if(luaThread
!= null)
627 luaThread
.interrupt();
628 terminateLuaVMAsync();
631 public void eci_luaplugin_clearconsole()
636 private void invokeCommand(String cmd
, String
[] args
)
638 if("luaplugin-terminate".equals(cmd
) && args
== null && luaThread
!= null) {
639 luaTerminateReq
= true;
640 if(luaThread
!= null)
641 luaThread
.interrupt();
642 terminateLuaVMAsync();
646 public void callInvokeCommand(String cmd
, String
[] args
, boolean sync
)
649 invokeCommand(cmd
, args
);
651 vPluginManager
.invokeExternalCommandSynchronous(cmd
, args
);
653 vPluginManager
.invokeExternalCommand(cmd
, args
);
656 public Object
[] callCommand(String cmd
, String
[] args
)
659 invokeCommand(cmd
, args
);
662 return vPluginManager
.invokeExternalCommandReturn(cmd
, args
);
665 public HardwareComponent
getComponent(Class
<?
> clazz
)
669 return pc
.getComponent(clazz
);
672 public void postMessage(String msg
)
674 queueEvent("message", msg
);
677 public void doReleaseVGA()
679 if(screenOut
!= null && ownsVGALine
&& ownsVGALock
)
680 screenOut
.releaseOutput(this);
682 vgaPoller
.reactivate();
685 public boolean getOwnsVGALock()
690 public int getXResolution()
692 if(screenOut
!= null && ownsVGALine
)
693 return screenOut
.getWidth();
697 public int getYResolution()
699 if(screenOut
!= null && ownsVGALine
)
700 return screenOut
.getHeight();
704 public boolean getPCConnected()
706 return (screenOut
!= null);
709 public boolean getPCRunning()
714 class VGARetraceWaiter
extends Thread
716 private volatile boolean active
;
717 private volatile boolean reactivateFlag
;
718 private volatile boolean deactivateFlag
;
720 public VGARetraceWaiter()
722 super("VGA Lua Trace waiting thread");
729 if(!active
|| !ownsVGALine
) {
730 //We are in quescent state. Wait for reactivation.
732 while(!reactivateFlag
)
735 } catch(Exception e
) {
738 reactivateFlag
= false;
740 boolean r
= screenOut
.waitOutput(LuaPlugin
.this);
743 queueEvent("lock", null);
748 deactivateFlag
= false;
755 public void deactivate()
759 deactivateFlag
= true;
767 } catch(Exception e
) {
773 public void reactivate()
777 reactivateFlag
= true;
784 public void queueEvent(String type
, String data
)
786 Event e
= new Event();
789 synchronized(eventQueue
) {
791 eventQueue
.notifyAll();
795 public Event
pollEvent()
797 synchronized(eventQueue
) {
798 return eventQueue
.poll();
802 public Event
waitEvent()
804 synchronized(eventQueue
) {
806 while((e
= eventQueue
.poll()) == null && !luaTerminateReq
&& !reconnectInProgress
)
809 } catch(Exception f
) {
822 private Queue
<Event
> eventQueue
;
824 public LuaPlugin(String args
) throws Exception
826 kernelArguments
= parseStringToComponents(args
);
827 userArguments
= new HashMap
<String
, String
>();
828 kernelName
= kernelArguments
.get("kernel");
829 kernelArguments
.remove("kernel");
831 vgaPoller
= new VGARetraceWaiter();
834 if(kernelArguments
.get("noguimode") != null)
835 this.specialNoGUIMode
= true;
837 this.pcRunning
= false;
838 this.luaThread
= null;
839 this.luaInvokeReq
= null;
840 this.luaTerminateReq
= false;
841 this.consoleMode
= true;
843 this.resources
= new HashMap
<String
, LuaResource
>();
844 this.liveObjects
= new IdentityHashMap
<LuaResource
, Integer
>();
845 this.eventQueue
= new LinkedList
<Event
>();
846 liveObjects
.put(null, null); //NULL is always considered live.
849 public LuaPlugin(Plugins manager
, String args
) throws Exception
853 this.consoleMode
= false;
854 this.vPluginManager
= manager
;
859 window
= new JFrame("Lua window" + Misc
.emuname
);
860 GridBagLayout layout
= new GridBagLayout();
861 GridBagConstraints c
= new GridBagConstraints();
862 panel
= new JPanel(layout
);
865 console
= new JTextArea(25, 80);
866 console
.setFont(new Font("Monospaced", Font
.PLAIN
, 12));
867 JScrollPane consoleScroller
= new JScrollPane(console
);
868 console
.setEditable(false);
869 c
.fill
= GridBagConstraints
.BOTH
;
874 panel
.add(consoleScroller
, c
);
876 execLabel
= new JLabel("Lua script");
877 c
.fill
= GridBagConstraints
.NONE
;
883 panel
.add(execLabel
, c
);
885 execName
= new JTextField("", 40);
886 c
.fill
= GridBagConstraints
.HORIZONTAL
;
891 panel
.add(execName
, c
);
893 execButton
= new JButton("Run");
894 c
.fill
= GridBagConstraints
.NONE
;
899 panel
.add(execButton
, c
);
900 execButton
.addActionListener(this);
902 termButton
= new JButton("Terminate Lua VM");
903 c
.fill
= GridBagConstraints
.NONE
;
908 panel
.add(termButton
, c
);
909 termButton
.addActionListener(this);
910 termButton
.setEnabled(false);
912 clearButton
= new JButton("Clear Console");
913 c
.fill
= GridBagConstraints
.NONE
;
918 panel
.add(clearButton
, c
);
919 clearButton
.addActionListener(this);
922 window
.setDefaultCloseOperation(JFrame
.DO_NOTHING_ON_CLOSE
);
923 Dimension d
= window
.getSize();
924 nativeWidth
= d
.width
;
925 nativeHeight
= d
.height
;
926 window
.setVisible(true);
929 class DedicatedShutdownHandler
extends Thread
937 class RunMainThread
implements Runnable
945 public static void main(String
[] args
)
947 if(args
.length
!= 2) {
948 System
.err
.println("Syntax: LuaPlugin <script> <args>");
955 p
= new LuaPlugin(args
[1]);
956 } catch(Exception e
) {
957 System
.err
.println("Can't initialize LuaPlugin: " + e
.getMessage());
962 Runtime
.getRuntime().addShutdownHook(p
.new DedicatedShutdownHandler());
963 Thread mThread
= new Thread(p
.new RunMainThread(), "Lua execution thread");
967 //Wait for main thread to become ready and send invoke request.
968 while(!p
.mainThreadWait
)
971 } catch(Exception e
) {
974 p
.invokeLuaVM(args
[0]);
975 } catch(Exception e
) {
976 System
.err
.println("Error: Can't start Lua VM: " + e
.getMessage());
979 //Wait for lua VM to finish.
980 while(p
.luaThread
!= null || p
.luaInvokeReq
!= null)
983 } catch(Exception e
) {
989 //Some extremely important callbacks.
990 public static int luaCB_print_console_msg(Lua l
, LuaPlugin plugin
)
992 if(l
.type(1) != Lua
.TSTRING
) {
993 l
.error("Unexpected types to print_console_msg");
996 plugin
.printConsoleMsg(l
.value(1).toString() + "\n");
1000 public static int luaCB_loadmodule(Lua l
, LuaPlugin plugin
)
1002 if(l
.type(1) != Lua
.TSTRING
) {
1003 l
.error("Unexpected types to loadmodule");
1007 Class
<?
> clazz
= Class
.forName(l
.checkString(1));
1008 LuaTable tab
= l
.newTable();
1009 plugin
.tableAddFunctions(l
, tab
, null, clazz
);
1011 } catch(Exception e
) {
1012 l
.error("No such extension module: " + l
.checkString(1));