File: technical.html

package info (click to toggle)
mips64emul 0.2.4-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 3,260 kB
  • ctags: 11,173
  • sloc: ansic: 43,056; sh: 535; asm: 326; makefile: 58
file content (576 lines) | stat: -rw-r--r-- 21,113 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
<html>
<head><title>mips64emul Technical Documentation</title>
</head>
<body bgcolor="#ffffff" text="#000000" link="#4040f0" vlink="#404040" alink="#ff0000">
<p> 
<table width=100%>
  <tr><td width=100% bgcolor=#808070><font color=#ffffe0 size=6>
  <b>mips64emul</b></font></td></tr>
</table>
<p>
<!-- The first 10 lines are cut away by the homepage updating script.  -->


<!--

$Id: technical.html,v 1.37 2005/01/10 23:21:31 debug Exp $

Copyright (C) 2004-2005  Anders Gavare.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

-->


<ul>
  <li><h3><a href="index.html">User Documentation</a></h3>
  <li><h3>Technical Documentation</h3>
</ul>

<hr>
<p><br>
<h2>mips64emul Technical Documentation</h2>

<p>
This page describes some of the internals of mips64emul. For more general 
documentation, please read the <a href="index.html">User
Documentation</a>.

<p><br>
<h3>Contents:</h3>

<p>
<ul>
  <li><a href="#overview">Overview</a>
  <li><a href="#speed">Speed</a>
  <li><a href="#net">Networking</a>
  <li><a href="#devices">Emulation of hardware devices</a>
  <li><a href="#regtest">Regression tests</a>
</ul>




<p><br>
<a name="overview"></a>
<h3>Overview</h3>

In simple terms, mips64emul is just a simple fetch-and-execute
loop; an instruction is fetched from memory, and executed.

<p>
In reality, a lot of things need to be handled. Before each instruction is 
executed, the emulator checks to see if any interrupts are asserted which 
are not masked away. If so, then an INT exception is generated. Exceptions 
cause the program counter to be set to a specific value, and some of the 
system coprocessor's registers to be set to values signifying what kind of 
exception it was (an interrupt exception in this case).

<p>
Reading instructions from memory is done through a TLB, a translation
lookaside buffer. The TLB on MIPS is software controlled, which means that 
the program running inside the emulator (for example an operating system 
kernel) has to take care of manually updating the TLB. Some memory 
addresses are translated into physical addresses directly, some are 
translated into valid physical addresses via the TLB, and some memory 
references are not valid. Invalid memory references cause exceptions.

<p>
After an instruction has been read from memory, the emulator checks which
opcode it contains and executes the instruction. Executing an instruction
usually involves reading some register and writing some register, or perhaps a 
load from memory (or a store to memory). The program counter is increased 
for every instruction.

<p>
Some memory references point to physical addresses which are not in the 
normal RAM address space. They may point to hardware devices. If that is 
the case, then loads and stores are converted into calls to a device 
access function. The device access function is then responsible for 
handling these reads and writes.  For example, a graphical framebuffer 
device may put a pixel on the screen when a value is written to it, or a 
serial controller device may output a character to stdout when written to.




<p><br>
<a name="speed"></a>
<h3>Speed</h3>

There are two modes in which the emulator can run, <b>a</b>) a straight forward 
loop which fetches one instruction from emulated RAM and executes it
(described in the previous section), and <b>b</b>)
using dynamic binary translation.

<p>
Mode <b>a</b> is very slow. On a 2.8 GHz Intel Xeon host the resulting 
emulated machine is rougly equal to a 7 MHz R3000 (or a 3.5 MHz R4000). 
The actual performance varies a lot, maybe between 5 and 10 million 
instructions per second, depending on workload.

<p>
Mode <b>b</b> ("bintrans") is still to be considered experimental, but 
gives higher performance than mode <b>a</b>. It translates MIPS machine 
code into machine code that can be executed on the host machine 
on-the-fly. The translation itself obviously takes some time, but this is 
usually made up for by the fact that the translated code chunks are 
executed multiple times.
To run the emulator with binary translation enabled, just add <b>-b</b>
to the command line.

<p>
Only small pieces of MIPS machine code are translated, usually the size of 
a function, or less. There is no "intermediate representation" code, so 
all translations are done directly from MIPS to host machine code.

<p>
The default bintrans cache size is 16 MB, but you can change this by adding
-DBINTRANS_SIZE_IN_MB=<i>xx</i> to your CFLAGS environment variable before 
running the configure script.

<p>
By default, an emulated OS running under DECstation emulation which listens to
interrupts from the mc146818 clock will get interrupts that are close to the
host's clock. That is, if the emulated OS says it wants 100 interrupts per
second, it will get approximately 100 interrupts per real second.

<p>
There is however a -I option, which sets the number of emulated cycles per
seconds to a fixed value. Let's say you wish to make the emulated OS think it
is running on a 40 MHz DECstation, and not a 7 MHz one, then you can add
-I 40000000 to the command line. This will not make the emulation faster, of
course. It might even make it seem slower; for example, if NetBSD/pmax waits
2 seconds for SCSI devices to settle during bootup, those 2 seconds will take
2*40000000 cycles (which will take more time than 2*7000000).

<p>
The -I option is also necessary if you want to run deterministic experiments,
if a mc146818 device is present.

<p>
Some emulators make claims such as "x times slowdown," but in the case of
mips64emul, the host is often not a MIPS-based machine, and hence comparing
one MIPS instruction to a host instruction doesn't work. Performance depends on
a lot of factors, including (but not limited to) host architecture, host speed,
which compiler and compiler flags were used to build mips64emul, what the
workload is, and so on. For example, if an emulated operating system tries
to read a block from disk, from its point of view the read was instantaneous
(no waiting). So 1 MIPS in an emulated OS might have taken more than one
million instructions on a real machine.  Because of this, imho it is best 
to measure performance as the actual (real-world) time it takes to perform 
a task with the emulator.




<p><br>
<a name="net"></a>
<h3>Networking</h3>

Running an entire operating system under emulation is very interesting in 
itself, but for several reasons, running a modern OS without access to 
TCP/IP networking is a bit akward. Hence, I feel the need to implement TCP/IP 
(networking) support in the emulator.

<p>
As far as I have understood it, there seems to be two different ways to go:

<ol>
  <li>Forward ethernet packets from the emulated ethernet controller to
	the host machine's ethernet controller, and capture incoming 
	packets on the host's controller, giving them back to the
	emulated OS. Characteristics are:
	<ul>
	  <li>Requires <i>direct</i> access to the host's NIC, which
		means on most platforms that the emulator cannot be
		run as a normal user!
	  <li>Reduced portability, as not every host operating system
		uses the same programming interface for dealing with
		hardware ethernet controllers directly.
	  <li>When run on a switched network, it might be problematic to
		connect from the emulated OS to the OS running on the
		host, as packets sent out on the host's NIC are not
		received by itself. (?)
	</ul>
  <p>
  or
  <p>
  <li>Whenever the emulated ethernet controller wishes to send a packet,
	the emulator looks at the packet and creates a response. Packets
	that can have an immediate response never go outside the emulator,
	other packet types have to be converted into suitable other
	connection types (UDP, TCP, etc). Characteristics:
	<ul>
	  <li>Each packet type sent out on the emulated NIC must be handled.
		This means that I have to do a lot of coding.
		(I like this, because it gives me an opportunity to
		learn about networking protocols.)
	  <li>By not relying on access to the host's NIC directly,
		portability is maintained. (It would be sad if the networking
		portion of a portable emulator isn't as portable as the
		rest of the emulator.)
	  <li>The emulator can be run as a normal user process, does
		not require root privilegies.
	  <li>Connecting from the emulated OS to the host's OS should
		not be problematic.
	  <li>The emulated OS will experience the network just as a single
		machine behind a NAT gateway/firewall would. The emulated
		OS is thus automatically protected from the outside world.
	</ul>
</ol>

Other emulators that I have heard of seem to use the first one, if they 
support networking.

<p>
Since I have choosen the second kind of implementation, I have to write 
support explicitly for any kind of network protocol that should be
supported. As of 2004-07-09, the following has been implemented and seems 
to work under at least NetBSD/pmax and OpenBSD/pmax under DECstation -D2
emulation:

<ul>
  <li>ARP requests sent out from the emulated NIC are interpreted,
	and converted to ARP responses. (This is used by the emulated OS
	to find out the MAC address of the gateway.)
  <li>ICMP echo requests (that is the kind of packet produced by the
	<b>ping</b> program) are interpreted and converted to ICMP echo
	replies, <i>regardless of the IP address</i>. This means that
	running ping from within the emulated OS will <i>always</i>
	receive a response. The ping packets never leave the emulated
	environment.
  <li>UDP packets are interpreted and passed along to the outside world.
	If the emulator receives an UDP packet from the outside world, it
	is converted into an UDP packet for the emulated OS. (This is not
	implemented very well yet, but seems to be enough for nameserver
	lookups, tftp file transfers, and NFS mounts using UDP.)
  <li>TCP packets are interpreted one at a time, similar to how UDP 
	packets are handled (but more state is kept for each connection).
	<font color="#ff0000">NOTE: Much of the TCP handling code is very
	ugly and hardcoded.</font>
  <li>RARP is not implemented yet. (I haven't needed it so far.)
</ul>

The gateway machine, which is the only "other" machine that the emulated 
OS sees on its emulated network, works as a NAT-style firewall/gateway. It 
has a fixed IPv4 address of 10.0.0.254. An OS running in the emulator 
can thus have any 10.x.x.x address; a typical choice would be 10.0.0.1.

<p>
Inside emulated NetBSD or OpenBSD, running the following commands should
configure the emulated NIC:
<pre>
	# <b>ifconfig le0 10.0.0.1</b>
	# <b>route add default 10.0.0.254</b>
	add net default: gateway 10.0.0.254
</pre>

If you want nameserver lookups to work, you need a valid /etc/resolv.conf 
as well:
<pre>
	# <b>echo nameserver 129.16.1.3 > /etc/resolv.conf</b>
</pre>
(But replace 129.16.1.3 with the actual real-world IP address of your
nearest nameserver.)
<p>
Now, host lookups should work:
<pre>
	# <b>host -a www.netbsd.org</b>
	Trying null domain
	rcode = 0 (Success), ancount=2
	The following answer is not authoritative:
	The following answer is not verified as authentic by the server:
	www.netbsd.org  86400 IN        AAAA    2001:4f8:4:7:290:27ff:feab:19a7
	www.netbsd.org  86400 IN        A       204.152.184.116
	For authoritative answers, see:
	netbsd.org      83627 IN        NS      uucp-gw-2.pa.dec.com
	netbsd.org      83627 IN        NS      ns.netbsd.org
	netbsd.org      83627 IN        NS      adns1.berkeley.edu
	netbsd.org      83627 IN        NS      adns2.berkeley.edu
	netbsd.org      83627 IN        NS      uucp-gw-1.pa.dec.com
	Additional information:
	ns.netbsd.org   83627 IN        A       204.152.184.164
	uucp-gw-1.pa.dec.com	172799 IN	A	204.123.2.18
	uucp-gw-2.pa.dec.com	172799 IN	A	204.123.2.19
</pre>

To transfer files via UDP, you can use the tftp program.

<pre>
	# <b>tftp 12.34.56.78</b>
	tftp> <b>get filename</b>
	Received XXXXXX bytes in X.X seconds
	tftp> <b>quit</b>
	# 
</pre>

or, to do it non-interactively (with ugly output):

<pre>
	# <b>echo get filename | tftp 12.34.56.78</b>
	tftp> Received XXXXXX bytes in X.X seconds
	tftp> #
</pre>

This, of course, requires that you have put the file <i>filename</i> in 
the root directory of the tftp server (12.34.56.78).

<p>
It is also possible to run NFS via UDP. This is very useful if you want to
share entire directory trees between the emulated environment and another
machine. These instruction will work for FreeBSD, if you are running
something else, use your imagination to modify them:

<ul>
  <li>On the server, add a line to your /etc/exports file, exporting
	the files you wish to use in the emulator:<pre>
	<b>/tftpboot -mapall=nobody -ro 123.11.22.33</b>
</pre>
	where 123.11.22.33 is the IP address of the machine running the
	emulator process, as seen from the outside world.
  <p>
  <li>Then start up the programs needed to serve NFS via UDP. Note the
	-n argument to mountd. This is needed to tell mountd to accept
	connections from unprivileged ports (because the emulator does
	not need to run as root).<pre>
	# <b>portmap</b>
	# <b>nfsd -u</b>       &lt;--- u for UDP
	# <b>mountd -n</b>
</pre>
  <li>In the guest OS in the emulator, once you have ethernet and IPv4
	configured so that you can use UDP, mounting the filesystem
	should now be possible:  (this example is for NetBSD/pmax
	or OpenBSD/pmax)<pre>
	# <b>mount -o ro,-r=1024,-w=1024,-U,-3 my.server.com:/tftpboot /mnt</b>
    or
	# <b>mount my.server.com:/tftpboot /mnt</b>
</pre>
	If you don't supply the read and write sizes, there is a risk
	that the default values are too large. The emulator currently
	does not handle fragmentation/defragmentation of <i>outgoing</i>
	packets, so going above the ethernet frame size (1518) is a very
	bad idea. Incoming packets (reading from nfs) should work, though,
	for example during an NFS install.
</ul>

The example above uses read-only mounts. That is enough for things like
letting NetBSD/pmax or OpenBSD/pmax install via NFS, without the need for
a CDROM ISO image. You can use a read-write mount if you wish to share
files in both directions, but then you should be aware of the 
fragmentation issue mentioned above.

<p>
TCP is implemented to some extent, but should not be considered to be
stable yet. It is enough to let NetBSD/pmax and OpenBSD/pmax install via 
ftp, though.




<p><br>
<a name="devices"></a>
<h3>Emulation of hardware devices</h3>

Each file in the device/ directory is responsible for one hardware
device. These are used from src/machine.c, when initializing which hardware a
particular machine model will be using.
(I'll be using the name 'foo' as the name of the device in all these
examples.  This is pseudo code, it might need some modification to 
actually compile and run.)
<p>

Each device should have the following:
<p>

<ul>
  <li>An init function in dev_foo.c. It would typically look
	something like this:
<pre>
	void dev_foo_init(struct cpu *cpu, struct memory *mem,
	    uint64_t baseaddr, int irq_nr)
	{
		struct foo_data *d;

		d = malloc(sizeof(struct foo_data));
		if (d == NULL) {
			fprintf(stderr, "out of memory\n");
			exit(1);
		}
		memset(d, 0, sizeof(struct foo_data));
		d->irq_nr = irq_nr;

		memory_device_register(mem, "foo", baseaddr,
		    DEV_FOO_LENGTH, dev_foo_access, d);

		/*  This should only be here if the device
		    has a tick function:  */
		cpu_add_tickfunction(cpu, dev_foo_tick, d,
		    FOO_TICKSHIFT);
	}
</pre><br>

  <li>The init function, the access function (described further down)
	and DEV_FOO_LENGTH should be defined in include/devices.h.
  <p>
  <li>At the top of dev_foo.c, the foo_data struct should be defined.
<pre>
	struct foo_data {
		int	irq_nr;
		/*  ...  */
	}
</pre><br>

  <li>If foo has a tick function (that is, something that needs to be
	run at regular intervals) then FOO_TICKSHIFT and a tick function
	need to be defined as well:
<pre>
	#define FOO_TICKSHIFT		10

	void dev_foo_tick(struct cpu *cpu, void *extra)
	{
		struct foo_data *d = (struct foo_data *) extra;

		if (.....)
			cpu_interrupt(cpu, d->irq_nr);
		else
			cpu_interrupt_ack(cpu, d->irq_nr);
	}
</pre><br>

  <li>And last but not least, the device should have an access function.
	The access function is called whenever there is a load or store
	to an address which is in the device' memory mapped region.
<pre>
	int dev_foo_access(struct cpu *cpu, struct memory *mem,
	    uint64_t relative_addr, unsigned char *data, size_t len,
	    int writeflag, void *extra)
	{
		struct foo_data *d = extra;
		uint64_t idata = 0, odata = 0;

		idata = memory_readmax64(cpu, data, len);
		switch (relative_addr) {
		/* .... */
		}

		if (writeflag == MEM_READ)
			memory_writemax64(cpu, data, len, odata);

		/*  Perhaps interrupts need to be asserted or
		    deasserted:  */
		dev_foo_tick(cpu, extra);

		/*  Return successfully.  */
		return 1;
	}
</pre><br>
</ul>
<p>
The return value of the access function has until 20040702 been a 
true/false value; 1 for success, or 0 for device access failure. A device 
access failure will be seen as a MIPS DBE exception from the CPU.
<p>
Right now I'm converting the devices to support arbitrary memory latency 
values. The return value is now the number of cycles that the read or 
write access took. A value of 1 means one cycle, a value of 10 means 10 
cycles. Negative values are used for device access failures, and the 
absolute value of the value is then the number of cycles; a value of -5 
means that the access failed, and took 5 cycles.
<p>
To be compatible with pre-20040702 devices, a return value of 0 is treated 
by the caller (in src/memory.c) as a value of -1.





<p><br>
<a name="regtest"></a>
<h3>Regression tests</h3>

In order to make sure that the emulator actually works like it is supposed 
to, it must be tested. For this purpose, there is a simple regression 
testing framework in the <b>tests/</b> directory.
<p>

<i>NOTE:  The regression testing framework is basically just a skeleton so 
far.
Regression tests are very good to have. However, the fact that complete
operating systems can run in the emulator indicate that the emulation is
probably not too incorrect. This makes it less of a priority to write 
regression tests.</i>

<p>
To run all the regression tests, type <b>make regtest</b>. Each assembly 
language file matching the pattern <b>test_*.S</b> will be compiled and 
linked into a 64-bit MIPS ELF (using a gcc cross compiler), and run in the 
emulator. If everything goes well, you should see something like this:

<pre>
	$ make regtest
	cd tests; make run_tests; cd ..
	gcc33 -Wall -fomit-frame-pointer -fmove-all-movables -fpeephole -O2 
		-mcpu=ev5 -I/usr/X11R6/include -lm -L/usr/X11R6/lib -lX11  do_tests.c
		-o do_tests
	do_tests.c: In function `main':
	do_tests.c:173: warning: unused variable `s'
	/var/tmp//ccFOupvD.o: In function `do_tests':
	/var/tmp//ccFOupvD.o(.text+0x3a8): warning: tmpnam() possibly used
		unsafely; consider using mkstemp()
	mips64-unknown-elf-gcc -g -O3 -fno-builtin -fschedule-insns -mips64 
		-mabi=64 test_common.c -c -o test_common.o
	./do_tests "mips64-unknown-elf-gcc -g -O3 -fno-builtin -fschedule-insns 
		-mips64 -mabi=64" "mips64-unknown-elf-as -mabi=64 -mips64" 
		"mips64-unknown-elf-ld -Ttext 0xa800000000030000 -e main 
		--oformat=elf64-bigmips" "../mips64emul"

	Starting tests:
	  test_addu.S
	  test_daddu.S
	  test_dummy.S
	  test_clo_clz.S
	  test_dclo_dclz.S

	Done. (5 tests done)
	    PASS:      5
	    FAIL:      0

	All tests OK
</pre>

<p>
Each test writes output to stdout, and there is a <b>test_*.good</b> for 
each <b>.S</b> file which contains the wanted output. If the actual output 
matches the <b>.good</b> file, then the test passes, otherwise it fails.

<p>
Read <b>tests/README</b> for more information.




</body>
</html>