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
.*;
41 import org
.jpc
.emulator
.PC
;
42 import org
.jpc
.emulator
.HardwareComponent
;
43 import org
.jpc
.emulator
.VGADigitalOut
;
44 import org
.jpc
.pluginsbase
.Plugins
;
45 import org
.jpc
.pluginsbase
.Plugin
;
46 import static org
.jpc
.Misc
.parseStringToComponents
;
47 import static org
.jpc
.Misc
.errorDialog
;
48 import static org
.jpc
.Misc
.moveWindow
;
50 //Locking this class is used for preventing termination and when terminating.
51 public class LuaPlugin
implements ActionListener
, Plugin
53 private JFrame window
;
55 private Plugins vPluginManager
;
56 private String kernelName
;
57 private Map
<String
, String
> kernelArguments
;
58 private Map
<String
, String
> userArguments
;
59 private int nativeWidth
;
60 private int nativeHeight
;
61 private JLabel execLabel
;
62 private JTextField execName
;
63 private JButton execButton
;
64 private JButton termButton
;
65 private JButton clearButton
;
66 private JTextArea console
;
68 private int nextHandle
;
70 //luaThread is null if Lua isn't running.
71 private Thread luaThread
;
73 private volatile boolean pcRunning
;
74 private volatile String luaInvokeReq
;
75 private volatile boolean luaTerminateReq
;
76 private volatile boolean luaTerminateReqAsync
;
77 private VGADigitalOut screenOut
;
79 private volatile boolean ownsVGALock
;
80 private volatile boolean ownsVGALine
;
81 private volatile boolean signalComplete
;
82 private volatile boolean luaStarted
;
83 private volatile boolean mainThreadWait
;
84 private volatile boolean reconnectInProgress
;
86 private boolean consoleMode
;
88 private Map
<String
, LuaResource
> resources
;
89 private IdentityHashMap
<LuaResource
, Integer
> liveObjects
;
90 private LinkedList
<String
> messageQueue
;
92 public static abstract class LuaResource
97 public LuaResource(LuaPlugin _plugin
)
99 handle
= "h" + (_plugin
.nextHandle
++);
101 plugin
.resources
.put(handle
, this);
104 public final String
getHandle()
109 public final void release(boolean noExceptions
) throws IOException
113 } catch(IOException e
) {
117 plugin
.liveObjects
.remove(this);
118 plugin
.resources
.remove(handle
);
121 public abstract void destroy() throws IOException
;
124 private String
getMethodHandle(Lua l
)
126 if(l
.type(1) == Lua
.TNONE
) {
127 l
.error("Handle required for method call");
130 l
.checkType(1, Lua
.TUSERDATA
);
131 Object _u
= l
.toUserdata(l
.value(1)).getUserdata();
132 if(!(_u
instanceof String
)) {
134 l
.error("Invalid handle to resource: " + _u
.getClass().getName());
136 l
.error("Invalid handle to resource: Null");
143 public void destroyLuaObject(Lua l
) throws IOException
145 String u
= getMethodHandle(l
);
146 LuaResource r1
= resources
.get(u
);
148 l
.error("Bad or closed handle passed to method");
153 public boolean systemShutdown()
155 //Just terminate the emulator.
159 public void reconnect(PC _pc
)
161 //Gat the thread out of VGA wait if its there.
162 reconnectInProgress
= true;
163 if(luaThread
!= null) {
164 luaThread
.interrupt();
167 reconnectInProgress
= false;
168 if(ownsVGALock
&& screenOut
!= null) {
169 screenOut
.releaseOutput(this);
172 if(ownsVGALine
&& screenOut
!= null) {
173 screenOut
.unsubscribeOutput(this);
177 screenOut
= _pc
.getVideoOutput();
183 if(screenOut
!= null && luaThread
!= null) {
184 screenOut
.subscribeOutput(this);
191 public void pcStarting()
196 public void pcStopping()
199 //Gat the thread out of VGA wait if its there.
200 reconnectInProgress
= true;
201 if(luaThread
!= null) {
202 luaThread
.interrupt();
205 reconnectInProgress
= false;
210 class LuaCallback
extends LuaJavaCallback
212 Method callbackMethod
;
215 LuaCallback(Object target
, Method callback
)
218 callbackMethod
= callback
;
221 public int luaFunction(Lua l
) {
222 synchronized(LuaPlugin
.this) {
224 if(!liveObjects
.containsKey(onObject
)) {
225 l
.error("Attempted to call method on dead object");
228 return ((Integer
)callbackMethod
.invoke(onObject
, luaState
, LuaPlugin
.this)).intValue();
229 } catch(InvocationTargetException e
) {
230 if(e
.getCause() instanceof LuaError
)
231 throw (LuaError
)e
.getCause(); //Pass runtime exceptions through.
232 errorDialog(e
.getCause(), "Error in callback", null, "Terminate Lua VM");
233 terminateLuaVMAsync();
234 } catch(Exception e
) {
235 if(e
instanceof LuaError
)
236 throw (LuaError
)e
; //Pass runtime exceptions through.
237 errorDialog(e
, "Error invoking callback", null, "Terminate Lua VM");
238 terminateLuaVMAsync();
245 public void tableAddFunctions(Lua l
, LuaTable table
, Object obj
, Class
<?
> clazz
)
248 clazz
= obj
.getClass();
249 //Add all exported callbacks.
250 Method
[] candidateMethods
= clazz
.getMethods();
251 for(Method candidate
: candidateMethods
) {
252 if(obj
!= null && Modifier
.isStatic(candidate
.getModifiers()))
253 continue; //Want non-static.
254 if(obj
== null && !Modifier
.isStatic(candidate
.getModifiers()))
255 continue; //Want static.
256 if(!Modifier
.isPublic(candidate
.getModifiers()))
257 continue; //Want public.
258 if(!candidate
.getName().startsWith("luaCB_"))
259 continue; //Not this...
260 String luaName
= candidate
.getName().substring(6);
261 Class
<?
>[] paramTypes
= candidate
.getParameterTypes();
262 Class
<?
> retType
= candidate
.getReturnType();
263 if(retType
!= int.class) {
264 System
.err
.println("Warning: Incorrect return type for " + candidate
.getName() +
265 ": " + retType
.getName() + ".");
268 if(paramTypes
== null || paramTypes
.length
!= 2) {
269 System
.err
.println("Warning: Incorrect parameter type for " + candidate
.getName() + ".");
272 if(paramTypes
[0] != Lua
.class || paramTypes
[1] != LuaPlugin
.class) {
273 System
.err
.println("Warning: Incorrect parameter type for " + candidate
.getName() + ".");
277 l
.setTable(table
, luaName
, new LuaCallback(obj
, candidate
));
281 public LuaUserdata
generateLuaClass(Lua l
, LuaResource towrap
)
283 LuaUserdata user
= new LuaUserdata(towrap
.getHandle());
284 LuaTable t
= l
.newTable();
285 tableAddFunctions(l
, t
, towrap
, null);
286 liveObjects
.put(towrap
, null);
287 l
.setTable(t
, "__index" , t
);
288 l
.setMetatable(user
, t
);
293 class LuaThread
implements Runnable
298 LuaThread(Lua _lua
, String _script
)
301 StringLib
.open(_lua
);
308 private String
describeFault(int r
)
310 if(r
== 0) return null;
311 else if(r
== Lua
.YIELD
) return "Main thread yielded.";
312 else if(r
== Lua
.ERRRUN
) return "Unprotected runtime error";
313 else if(r
== Lua
.ERRSYNTAX
) return "syntax error";
314 //else if(r == Lua.ERRMEM) return "Out of memory");
315 else if(r
== Lua
.ERRFILE
) return "I/O error loading";
316 else if(r
== Lua
.ERRERR
) return "Double fault";
317 else return "Unknown fault #" + r
;
324 lua
.setGlobal("script", script
);
326 lua
.setGlobal("args", sTable
= lua
.newTable());
327 for(Map
.Entry
<String
, String
> x
: kernelArguments
.entrySet())
328 lua
.setTable(sTable
, x
.getKey(), x
.getValue());
329 for(Map
.Entry
<String
, String
> x
: userArguments
.entrySet())
330 lua
.setTable(sTable
, "x-" + x
.getKey(), x
.getValue());
332 tableAddFunctions(lua
, lua
.getGlobals(), null, LuaPlugin
.class);
334 //Wait for lua startup to be signaled in order to avoid deadlocks.
338 } catch(Exception e
) {
341 InputStream kernel
= null;
343 kernel
= new BufferedInputStream(new FileInputStream(kernelName
));
344 int r
= lua
.load(kernel
, "Kernel");
345 String fault
= describeFault(r
);
347 throw new Exception("Kernel loading error: " + fault
);
348 r
= lua
.pcall(0, 0, null);
349 fault
= describeFault(r
);
351 throw new Exception("Kernel error: " + fault
);
352 } catch(Exception e
) {
353 printConsoleMsg("\n\nLua Error: " + e
.getMessage() + "\n" +
354 lua
.value(-1).toString() + "\n\n");
355 //e.printStackTrace();
356 errorDialog(e
, "Lua error", null, "Dismiss");
358 //Lua script quit. Terminate the VM.
359 synchronized(LuaPlugin
.this) {
360 cleanupLuaResources();
363 LuaPlugin
.this.notifyAll();
365 printConsoleMsg("Lua VM: Lua script finished.\n");
369 private void cleanupLuaResources()
372 screenOut
.releaseOutput(LuaPlugin
.this);
376 screenOut
.unsubscribeOutput(LuaPlugin
.this);
380 while(resources
.size() > 0) {
381 Map
.Entry
<String
, LuaResource
> entry
= resources
.entrySet().iterator().next();
382 String key
= entry
.getKey();
383 LuaResource obj
= entry
.getValue();
386 } catch(Exception e
) {
388 resources
.remove(key
);
389 liveObjects
.remove(obj
);
399 mainThreadWait
= true;
401 if(luaInvokeReq
== null && !luaTerminateReq
)
403 mainThreadWait
= false;
405 } catch(Exception e
) {
408 if(luaInvokeReq
!= null && luaThread
== null) {
410 if(screenOut
!= null && !ownsVGALine
) {
411 screenOut
.subscribeOutput(this);
415 luaState
= new Lua();
416 luaThread
= new Thread(new LuaThread(luaState
, luaInvokeReq
));
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
;
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.
521 signalComplete
= false;
522 luaTerminateReq
= true;
523 luaTerminateReqAsync
= false;
525 synchronized(messageQueue
) {
526 messageQueue
.notifyAll();
528 while(luaThread
!= null)
531 } catch(Exception e
) {
537 private synchronized void terminateLuaVMAsync()
539 if(luaThread
== null)
542 //This request won't go to lua execution thread.
544 signalComplete
= false;
545 luaTerminateReq
= true;
546 luaTerminateReqAsync
= true;
548 synchronized(messageQueue
) {
549 messageQueue
.notifyAll();
553 private void setLuaButtons()
558 if(!SwingUtilities
.isEventDispatchThread())
560 SwingUtilities
.invokeAndWait(new Thread() { public void run() {
561 LuaPlugin
.this.execButton
.setText((luaThread
== null) ?
"Run" : "Send");
562 LuaPlugin
.this.termButton
.setEnabled(luaThread
!= null);
564 } catch(Exception e
) {
567 LuaPlugin
.this.execButton
.setText((luaThread
== null) ?
"Run" : "Send");
568 LuaPlugin
.this.termButton
.setEnabled(luaThread
!= null);
572 private void clearConsole()
577 if(!SwingUtilities
.isEventDispatchThread())
579 SwingUtilities
.invokeAndWait(new Thread() { public void run() {
582 } catch(Exception e
) {
589 public void actionPerformed(ActionEvent evt
)
591 if(evt
.getSource() == execButton
) {
593 if(luaThread
== null)
594 invokeLuaVM(execName
.getText());
596 postMessage(execName
.getText());
597 } catch(Exception e
) {
598 printConsoleMsg("Lua script starting / message send error: " + e
.getMessage());
600 } else if(evt
.getSource() == termButton
) {
601 luaTerminateReq
= true;
602 if(luaThread
!= null)
603 luaThread
.interrupt();
605 } else if(evt
.getSource() == clearButton
) {
610 public void eci_luaplugin_setwinpos(Integer x
, Integer y
)
612 moveWindow(window
, x
.intValue(), y
.intValue(), nativeWidth
, nativeHeight
);
615 public void eci_luaplugin_run(String script
)
617 if(luaThread
== null)
620 } catch(Exception e
) {
621 printConsoleMsg("Lua script starting error: " + e
.getMessage());
625 public void eci_luaplugin_terminate()
627 luaTerminateReq
= true;
628 if(luaThread
!= null)
629 luaThread
.interrupt();
630 terminateLuaVMAsync();
633 public void eci_luaplugin_clearconsole()
638 private void invokeCommand(String cmd
, String
[] args
)
640 if("luaplugin-terminate".equals(cmd
) && args
== null && luaThread
!= null) {
641 luaTerminateReq
= true;
642 if(luaThread
!= null)
643 luaThread
.interrupt();
644 terminateLuaVMAsync();
648 public void callInvokeCommand(String cmd
, String
[] args
, boolean sync
)
651 invokeCommand(cmd
, args
);
653 vPluginManager
.invokeExternalCommandSynchronous(cmd
, args
);
655 vPluginManager
.invokeExternalCommand(cmd
, args
);
658 public Object
[] callCommand(String cmd
, String
[] args
)
661 invokeCommand(cmd
, args
);
664 return vPluginManager
.invokeExternalCommandReturn(cmd
, args
);
667 public HardwareComponent
getComponent(Class
<?
extends HardwareComponent
> clazz
)
671 return pc
.getComponent(clazz
);
674 public synchronized void waitPCAttach()
676 while(screenOut
== null && !luaTerminateReq
) {
679 } catch(InterruptedException e
) {
684 public synchronized boolean waitPCStop()
686 //Temporarily release VGA output line to avoid deadlocking.
687 if(ownsVGALock
&& screenOut
!= null) {
688 screenOut
.releaseOutput(this);
691 if(ownsVGALine
&& screenOut
!= null) {
692 screenOut
.unsubscribeOutput(this);
696 while(pcRunning
&& screenOut
!= null && !luaTerminateReq
) {
699 } catch(InterruptedException e
) {
703 if(screenOut
!= null && !luaTerminateReq
) {
704 screenOut
.subscribeOutput(this);
712 public synchronized String
waitMessage()
715 synchronized(messageQueue
) {
716 while((msg
= messageQueue
.poll()) == null && !luaTerminateReq
) {
719 } catch(InterruptedException e
) {
726 public synchronized String
pollMessage()
729 synchronized(messageQueue
) {
730 msg
= messageQueue
.poll();
735 public void postMessage(String msg
)
737 synchronized(messageQueue
) {
738 messageQueue
.add(msg
);
739 messageQueue
.notifyAll();
743 public void clearMessages()
745 synchronized(messageQueue
) {
746 messageQueue
.clear();
750 public void doLockVGA()
752 if(screenOut
!= null && ownsVGALine
&& !ownsVGALock
&& !luaTerminateReq
&& !reconnectInProgress
)
753 if(screenOut
.waitOutput(this))
757 public void doReleaseVGA()
759 if(screenOut
!= null && ownsVGALine
&& ownsVGALock
)
760 screenOut
.releaseOutput(this);
764 public boolean getOwnsVGALock()
769 public int getXResolution()
771 if(screenOut
!= null && ownsVGALine
)
772 return screenOut
.getWidth();
776 public int getYResolution()
778 if(screenOut
!= null && ownsVGALine
)
779 return screenOut
.getHeight();
783 public boolean getPCConnected()
785 return (screenOut
!= null);
788 public boolean getPCRunning()
793 public LuaPlugin(String args
) throws Exception
795 kernelArguments
= parseStringToComponents(args
);
796 userArguments
= new HashMap
<String
, String
>();
797 kernelName
= kernelArguments
.get("kernel");
798 kernelArguments
.remove("kernel");
799 if(kernelName
== null)
800 throw new IOException("Kernel name (kernel) required for LuaPlugin");
802 this.pcRunning
= false;
803 this.luaThread
= null;
804 this.luaInvokeReq
= null;
805 this.luaTerminateReq
= false;
806 this.consoleMode
= true;
808 this.resources
= new HashMap
<String
, LuaResource
>();
809 this.liveObjects
= new IdentityHashMap
<LuaResource
, Integer
>();
810 this.messageQueue
= new LinkedList
<String
>();
811 liveObjects
.put(null, null); //NULL is always considered live.
814 public LuaPlugin(Plugins manager
, String args
) throws Exception
818 this.consoleMode
= false;
819 this.vPluginManager
= manager
;
821 window
= new JFrame("Lua window");
822 GridBagLayout layout
= new GridBagLayout();
823 GridBagConstraints c
= new GridBagConstraints();
824 panel
= new JPanel(layout
);
827 console
= new JTextArea(25, 80);
828 JScrollPane consoleScroller
= new JScrollPane(console
);
829 console
.setEditable(false);
830 c
.fill
= GridBagConstraints
.HORIZONTAL
;
834 panel
.add(consoleScroller
, c
);
836 execLabel
= new JLabel("Lua script");
837 c
.fill
= GridBagConstraints
.NONE
;
842 panel
.add(execLabel
, c
);
844 execName
= new JTextField("", 40);
845 c
.fill
= GridBagConstraints
.HORIZONTAL
;
850 panel
.add(execName
, c
);
852 execButton
= new JButton("Run");
853 c
.fill
= GridBagConstraints
.NONE
;
858 panel
.add(execButton
, c
);
859 execButton
.addActionListener(this);
861 termButton
= new JButton("Terminate Lua VM");
862 c
.fill
= GridBagConstraints
.NONE
;
867 panel
.add(termButton
, c
);
868 termButton
.addActionListener(this);
869 termButton
.setEnabled(false);
871 clearButton
= new JButton("Clear Console");
872 c
.fill
= GridBagConstraints
.NONE
;
877 panel
.add(clearButton
, c
);
878 clearButton
.addActionListener(this);
881 window
.setDefaultCloseOperation(JFrame
.DO_NOTHING_ON_CLOSE
);
882 Dimension d
= window
.getSize();
883 nativeWidth
= d
.width
;
884 nativeHeight
= d
.height
;
885 window
.setVisible(true);
888 class DedicatedShutdownHandler
extends Thread
896 class RunMainThread
implements Runnable
904 public static void main(String
[] args
)
906 if(args
.length
!= 2) {
907 System
.err
.println("Syntax: LuaPlugin <script> <args>");
914 p
= new LuaPlugin(args
[1]);
915 } catch(Exception e
) {
916 System
.err
.println("Can't initialize LuaPlugin: " + e
.getMessage());
921 Runtime
.getRuntime().addShutdownHook(p
.new DedicatedShutdownHandler());
922 Thread mThread
= new Thread(p
.new RunMainThread());
926 //Wait for main thread to become ready and send invoke request.
927 while(!p
.mainThreadWait
)
930 } catch(Exception e
) {
933 p
.invokeLuaVM(args
[0]);
934 } catch(Exception e
) {
935 System
.err
.println("Error: Can't start Lua VM: " + e
.getMessage());
938 //Wait for lua VM to finish.
939 while(p
.luaThread
!= null || p
.luaInvokeReq
!= null)
942 } catch(Exception e
) {
948 //Some extremely important callbacks.
949 public static int luaCB_print_console_msg(Lua l
, LuaPlugin plugin
)
951 if(l
.type(1) != Lua
.TSTRING
) {
952 l
.error("Unexpected types to print_console_msg");
955 plugin
.printConsoleMsg(l
.value(1).toString() + "\n");
959 public static int luaCB_loadmodule(Lua l
, LuaPlugin plugin
)
961 if(l
.type(1) != Lua
.TSTRING
) {
962 l
.error("Unexpected types to loadmodule");
966 Class
<?
> clazz
= Class
.forName(l
.checkString(1));
967 LuaTable tab
= l
.newTable();
968 plugin
.tableAddFunctions(l
, tab
, null, clazz
);
970 } catch(Exception e
) {
971 l
.error("No such extension module: " + l
.checkString(1));