Commit | Line | Data |
---|---|---|
44986d67 MD |
1 | Date: Tue, 11 Jan 94 13:23:11 -0800 |
2 | From: "pardo@cs.washington.edu" <pardo@meitner.cs.washington.edu> | |
3 | ||
4 | >[What's needed to get `qt' on an i860-based machine?] | |
5 | ||
6 | Almost certainly "some assembly required" (pun accepted). | |
7 | ||
8 | To write a cswap port, you need to understand the context switching | |
9 | model. Turn to figure 2 in the QT TR. Here's about what the assembly | |
10 | code looks like to implement that: | |
11 | ||
12 | qt_cswap: | |
13 | adjust stack pointer | |
14 | save callee-save registers on to old's stack | |
15 | argument register <- old sp | |
16 | sp <- new sp | |
17 | (*helper)(args...) | |
18 | restore callee-save registers from new's stack | |
19 | unadjust stack pointer | |
20 | return | |
21 | ||
22 | Once more in slow motion: | |
23 | ||
24 | - `old' thread calls context switch routine (new, a0, a1, h) | |
25 | - cswap routine saves registers that have useful values | |
26 | - cswap routine switches to new stack | |
27 | - cswap routine calls helper function (*h)(old, a0, a1) | |
28 | - when helper returns, cswap routine restores registers | |
29 | that were saved the last time `new' was suspended | |
30 | - cswap routine returns to whatever `new' routine called the | |
31 | context switch routine | |
32 | ||
33 | There's a few tricks here. First, how do you start a thread running | |
34 | for the very first time? Answer is: fake some stuff on the stack | |
35 | so it *looks* like it was called from the middle of some routine. | |
36 | When the new thread is restarted, it is treated like any other | |
37 | thread. It just so happens that it's never really run before, but | |
38 | you can't tell that because the saved state makes it look like like | |
39 | it's been run. The return pc is set to point at a little stub of | |
40 | assembly code that loads up registers with the right values and | |
41 | then calls `only'. | |
42 | ||
43 | Second, I advise you to forget about varargs routines (at least | |
44 | until you get single-arg routines up and running). | |
45 | ||
46 | Third, on most machines `qt_abort' is the same as `qt_cswap' except | |
47 | that it need not save any callee-save registers. | |
48 | ||
49 | Fourth, `qt_cswap' needs to save and restore any floating-point | |
50 | registers that are callee-save (see your processor handbook). On | |
51 | some machines, *no* floating-point registers are callee-save, so | |
52 | `qt_cswap' is exactly the same as the integer-only cswap routine. | |
53 | ||
54 | I suggest staring at the MIPS code for a few minutes. It's "mostly" | |
55 | generic RISC code, so it gets a lot of the flavor across without | |
56 | getting too bogged down in little nitty details. | |
57 | ||
58 | ||
59 | ||
60 | Now for a bit more detail: The stack is laid out to hold callee-save | |
61 | registers. On many machines, I implemented fp cswap as save fp | |
62 | regs, call integer cswap, and when integer cswap returns (when the | |
63 | thread wakes up again), restore fp regs. | |
64 | ||
65 | For thread startup, I figure out some callee-save registers that | |
66 | I use to hold parameters to the startup routine (`only'). When | |
67 | the thread is being started it doesn't have any saved registers | |
68 | that need to be restored, but I go ahead and let the integer context | |
69 | switch routine restore some registers then "return" to the stub | |
70 | code. The stub code then copies the "callee save" registers to | |
71 | argument registers and calls the startup routine. That keeps the | |
72 | stub code pretty darn simple. | |
73 | ||
74 | For each machine I need to know the machine's procedure calling | |
75 | convention before I write a port. I figure out how many callee-save | |
76 | registers are there and allocate enough stack space for those | |
77 | registers. I also figure out how parameters are passed, since I | |
78 | will need to call the helper function. On most RISC machines, I | |
79 | just need to put the old sp in the 0'th arg register and then call | |
80 | indirect through the 3rd arg register; the 1st and 2nd arg registers | |
81 | are already set up correctly. Likewise, I don't touch the return | |
82 | value register between the helper's return and the context switch | |
83 | routine's return. | |
84 | ||
85 | I have a bunch of macros set up to do the stack initialization. | |
86 | The easiest way to debug this stuff is to go ahead and write a C | |
87 | routine to do stack initialization. Once you're happy with it you | |
88 | can turn it in to a macro. | |
89 | ||
90 | In general there's a lot of ugly macros, but most of them do simple | |
91 | things like return constants, etc. Any time you're looking at it | |
92 | and it looks confusing you just need to remember "this is actually | |
93 | simple code, the only tricky thing is calling the helper between | |
94 | the stack switch and the new thread's register restore." | |
95 | ||
96 | ||
97 | You will almost certainly need to write the assembly code fragment | |
98 | that starts a thread. You might be able to do a lot of the context | |
99 | switch code with `setjmp' and `longjmp', if they *happen* to have | |
100 | the "right" implementation. But getting all the details right (the | |
101 | helper can return a value to the new thread's cswap routine caller) | |
102 | is probaby trickier than writing code that does the minimum and | |
103 | thus doesn't have any extra instructions (or generality) to cause | |
104 | problems. | |
105 | ||
106 | I don't know of any ports besides those included with the source | |
107 | code distribution. If you send me a port I will hapily add it to | |
108 | the distribution. | |
109 | ||
110 | Let me know as you have questions and/or comments. | |
111 | ||
112 | ;-D on ( Now *that*'s a switch... ) Pardo |