1 | /* |
---|
2 | * Simple interface to the PowerPC 405 MMU |
---|
3 | * |
---|
4 | * Michael Hamel ADInstruments 2008 |
---|
5 | * |
---|
6 | */ |
---|
7 | |
---|
8 | |
---|
9 | #include <bsp.h> |
---|
10 | #include <libcpu/powerpc-utility.h> |
---|
11 | #include "mmu_405.h" |
---|
12 | |
---|
13 | /* #define qLogTLB */ |
---|
14 | /* #define qLogTLBDetails */ |
---|
15 | |
---|
16 | |
---|
17 | /*--------------------------------- TLB handling ------------------------------------- */ |
---|
18 | /* The following are in assembler in mmu_405asm.S */ |
---|
19 | extern void MMU_GetTLBEntry(uint8_t index, uint32_t* tagword, uint32_t* dataword, uint8_t* pid); |
---|
20 | extern void MMU_SetTLBEntry(uint8_t index, uint32_t hiword, uint32_t loword, uint8_t pid); |
---|
21 | extern void MMU_ClearTLBs(); |
---|
22 | extern int16_t MMU_FindTLBEntry(uint32_t address); |
---|
23 | |
---|
24 | |
---|
25 | enum { kNTLBs = 64 }; /* for 403GCX and 405 */ |
---|
26 | |
---|
27 | static bool sFreeTLBs[kNTLBs]; |
---|
28 | static uint8_t sLastIndex = 0; |
---|
29 | static int sNInUse = 0; |
---|
30 | |
---|
31 | static void MMUFault(const char* what) |
---|
32 | /* Used for all setup faults; these can't really be ignored */ |
---|
33 | { |
---|
34 | printk("\n>>>MMU fatal error %s\n",what); |
---|
35 | rtems_fatal_error_occurred(RTEMS_INTERNAL_ERROR); |
---|
36 | } |
---|
37 | |
---|
38 | static uint8_t AllocTLB() |
---|
39 | { |
---|
40 | uint8_t index; |
---|
41 | |
---|
42 | index = sLastIndex; |
---|
43 | do { |
---|
44 | index++; |
---|
45 | if (index == kNTLBs) |
---|
46 | index = 0; |
---|
47 | if (index == sLastIndex) |
---|
48 | MMUFault("TLB table full"); |
---|
49 | } while (! sFreeTLBs[index]); |
---|
50 | sFreeTLBs[index] = false; |
---|
51 | sLastIndex = index; |
---|
52 | sNInUse++; |
---|
53 | return index; |
---|
54 | } |
---|
55 | |
---|
56 | static void FreeTLB(uint8_t index) |
---|
57 | { |
---|
58 | MMU_SetTLBEntry(index,0,0,0); |
---|
59 | sFreeTLBs[index] = true; |
---|
60 | sLastIndex = index-1; |
---|
61 | sNInUse--; |
---|
62 | } |
---|
63 | |
---|
64 | |
---|
65 | /*---------------------------- MMU operations ---------------------------------- */ |
---|
66 | |
---|
67 | int DataMissException(BSP_Exception_frame *f, unsigned int vector); |
---|
68 | int InstructionMissException(BSP_Exception_frame *f, unsigned int vector); |
---|
69 | int InstructionFetchException(BSP_Exception_frame *f, unsigned int vector); |
---|
70 | |
---|
71 | void |
---|
72 | mmu_initialise() |
---|
73 | /* Clear the TLBs and set up exception handlers for the MMU miss handlers */ |
---|
74 | { |
---|
75 | int i; |
---|
76 | |
---|
77 | MMU_ClearTLBs(); |
---|
78 | for (i=0; i<kNTLBs; i++) { |
---|
79 | sFreeTLBs[i] = true; |
---|
80 | MMU_SetTLBEntry(i,0,0,0xFF); |
---|
81 | } |
---|
82 | ppc_exc_set_handler(ASM_ISI_VECTOR ,InstructionFetchException); |
---|
83 | ppc_exc_set_handler(ASM_BOOKE_ITLBMISS_VECTOR ,DataMissException); |
---|
84 | ppc_exc_set_handler(ASM_BOOKE_DTLBMISS_VECTOR ,InstructionMissException); |
---|
85 | } |
---|
86 | |
---|
87 | static void |
---|
88 | MakeTLBEntries(uint32_t startAt, uint32_t nBytes, bool EX, bool WR, bool I, uint8_t PID) |
---|
89 | { |
---|
90 | uint32_t mask, options, tagWord, dataWord; |
---|
91 | uint8_t index, sizeCode, pid; |
---|
92 | |
---|
93 | if ((startAt & 0x3FF) != 0) |
---|
94 | MMUFault("TLB entry not on 1K boundary"); |
---|
95 | if ((nBytes & 0x3FF) != 0) |
---|
96 | MMUFault("TLB size not on 1K boundary"); |
---|
97 | |
---|
98 | options = 0; |
---|
99 | if (EX) options += 0x200; |
---|
100 | if (WR) options += 0x100; |
---|
101 | if (I) options += 5; |
---|
102 | |
---|
103 | #ifdef qLogTLB |
---|
104 | printk("TLB: make entries for $%X bytes from $%X..$%X PID %d",nBytes, startAt, startAt+nBytes-1, PID); |
---|
105 | if (EX) printk(" EX"); |
---|
106 | if (WR) printk(" WR"); |
---|
107 | if (I) printk(" I"); |
---|
108 | printk("\n"); |
---|
109 | #endif |
---|
110 | |
---|
111 | while (nBytes > 0) { |
---|
112 | /* Find the largest block we can base on this address */ |
---|
113 | mask = 0x3FF; |
---|
114 | sizeCode = 0; |
---|
115 | while (mask < nBytes && ((startAt & mask)==0) && sizeCode < 8) { |
---|
116 | mask = (mask<<2) + 3; |
---|
117 | sizeCode++; |
---|
118 | } |
---|
119 | mask >>= 2; |
---|
120 | sizeCode--; |
---|
121 | |
---|
122 | /* Make a TLB entry describing this, ZSEL=0 */ |
---|
123 | tagWord = startAt | (sizeCode<<7) | 0x40; |
---|
124 | dataWord = startAt | options; |
---|
125 | index = AllocTLB(); |
---|
126 | MMU_SetTLBEntry( index , tagWord, dataWord, PID); |
---|
127 | |
---|
128 | { |
---|
129 | /* Paranoia: check that we can read that back... */ |
---|
130 | uint8_t tdex, oldpid; |
---|
131 | |
---|
132 | oldpid = mmu_current_processID(); |
---|
133 | mmu_set_processID(PID); |
---|
134 | tdex = MMU_FindTLBEntry(startAt); |
---|
135 | mmu_set_processID(oldpid); |
---|
136 | |
---|
137 | if (tdex != index) { |
---|
138 | printk(" Add TLB %d: At %X for $%X sizecode %d tagWord $%X ",index, startAt, mask+1,sizeCode,tagWord); |
---|
139 | printk(" -- find failed, %d/%d!\n",tdex,index); |
---|
140 | MMU_GetTLBEntry(index, &tagWord, &dataWord, &pid); |
---|
141 | printk(" -- reads back $%X : $%X, PID %d\n",tagWord,dataWord,pid); |
---|
142 | } else { |
---|
143 | #ifdef qLogTLBDetails |
---|
144 | printk(" Add TLB %d: At %X for $%X sizecode %d tagWord $%X\n",index, startAt, mask+1,sizeCode,tagWord); |
---|
145 | #endif |
---|
146 | } |
---|
147 | } |
---|
148 | |
---|
149 | /* Subtract block from startAddr and nBytes */ |
---|
150 | mask++; /* Convert to a byte count */ |
---|
151 | startAt += mask; |
---|
152 | nBytes -= mask; |
---|
153 | } |
---|
154 | #ifdef qLogTLB |
---|
155 | printk(" %d in use\n",sNInUse); |
---|
156 | #endif |
---|
157 | } |
---|
158 | |
---|
159 | void |
---|
160 | mmu_remove_space(uint32_t startAt, uint32_t endAt) |
---|
161 | { |
---|
162 | int16_t index; |
---|
163 | int32_t size; |
---|
164 | uint32_t tagword, dataword, nBytes; |
---|
165 | uint8_t pid, sCode; |
---|
166 | |
---|
167 | nBytes = endAt - startAt; |
---|
168 | |
---|
169 | #ifdef qLogTLB |
---|
170 | printk("TLB: delete entries for $%X bytes from $%X\n",nBytes,startAt); |
---|
171 | #endif |
---|
172 | |
---|
173 | while (nBytes > 0) { |
---|
174 | index = MMU_FindTLBEntry( (uint32_t)startAt ); |
---|
175 | size = 1024; |
---|
176 | if (index >= 0) { |
---|
177 | MMU_GetTLBEntry(index, &tagword, &dataword, &pid); |
---|
178 | if ((tagword & 0x40) == 0) |
---|
179 | MMUFault("Undefine failed: redundant entries?"); |
---|
180 | if ((tagword & 0xFFFFFC00) != (uint32_t)startAt) |
---|
181 | MMUFault("Undefine not on TLB boundary"); |
---|
182 | FreeTLB(index); |
---|
183 | sCode = (tagword >> 7) & 7; |
---|
184 | while (sCode > 0) { |
---|
185 | size <<= 2; |
---|
186 | sCode--; |
---|
187 | } |
---|
188 | #ifdef qLogTLBDetails |
---|
189 | printk(" Free TLB %d: At %X for $%X\n",index, startAt, size); |
---|
190 | #endif |
---|
191 | } |
---|
192 | startAt += size; |
---|
193 | nBytes -= size; |
---|
194 | } |
---|
195 | } |
---|
196 | |
---|
197 | void |
---|
198 | mmu_add_space(uint32_t startAddr, uint32_t endAddr, MMUAccessType permissions, uint8_t processID) |
---|
199 | /* Convert accesstype to write-enable, executable, and cache-inhibit bits */ |
---|
200 | { |
---|
201 | bool EX, WR, I; |
---|
202 | |
---|
203 | EX = false; |
---|
204 | WR = false; |
---|
205 | I = false; |
---|
206 | switch (permissions) { |
---|
207 | case executable : EX = true; break; |
---|
208 | case readOnlyData : break; |
---|
209 | case readOnlyNoCache : I = true; break; |
---|
210 | case readWriteData : WR = true; break; |
---|
211 | case readWriteNoCache : WR = true; I= true; break; |
---|
212 | case readWriteExecutable: WR = true; EX = true; break; |
---|
213 | } |
---|
214 | MakeTLBEntries( (uint32_t)startAddr, (uint32_t)(endAddr-startAddr+1), EX, WR, I, processID); |
---|
215 | } |
---|
216 | |
---|
217 | int |
---|
218 | mmu_get_tlb_count() |
---|
219 | { |
---|
220 | return sNInUse; |
---|
221 | } |
---|
222 | |
---|
223 | /*---------------------------- CPU process ID handling ---------------------------------- |
---|
224 | * Really dumb system where we just hand out sequential numbers and eventually fail |
---|
225 | * As long as we only use 8-9 processes this isn't a problem */ |
---|
226 | |
---|
227 | static uint8_t sNextPID = 1; |
---|
228 | |
---|
229 | #define SPR_PID 0x3B1 |
---|
230 | |
---|
231 | uint8_t mmu_new_processID() |
---|
232 | { |
---|
233 | return sNextPID++; |
---|
234 | } |
---|
235 | |
---|
236 | void mmu_free_processID(uint8_t freeThis) |
---|
237 | { |
---|
238 | } |
---|
239 | |
---|
240 | uint8_t mmu_current_processID() |
---|
241 | { |
---|
242 | return PPC_SPECIAL_PURPOSE_REGISTER(SPR_PID); |
---|
243 | } |
---|
244 | |
---|
245 | uint8_t mmu_set_processID(uint8_t newID) |
---|
246 | { |
---|
247 | uint8_t prev = mmu_current_processID(); |
---|
248 | PPC_SET_SPECIAL_PURPOSE_REGISTER(SPR_PID,newID); |
---|
249 | return prev; |
---|
250 | } |
---|
251 | |
---|
252 | |
---|
253 | /* ------------------ Fault handlers ------------------ */ |
---|
254 | |
---|
255 | #define SPR_ESR 0x3D4 |
---|
256 | #define SPR_DEAR 0x3D5 |
---|
257 | |
---|
258 | enum { kESR_DST = 0x00800000 }; |
---|
259 | |
---|
260 | int DataMissException(BSP_Exception_frame *f, unsigned int vector) |
---|
261 | { |
---|
262 | uint32_t addr, excSyn; |
---|
263 | |
---|
264 | addr = PPC_SPECIAL_PURPOSE_REGISTER(SPR_DEAR); |
---|
265 | excSyn = PPC_SPECIAL_PURPOSE_REGISTER(SPR_ESR); |
---|
266 | if (excSyn & kESR_DST) printk("\n---Data write to $%X attempted at $%X\n",addr,f->EXC_SRR0); |
---|
267 | else printk("\n---Data read from $%X attempted at $%X\n",addr,f->EXC_SRR0); |
---|
268 | return -1; |
---|
269 | } |
---|
270 | |
---|
271 | int InstructionMissException(BSP_Exception_frame *f, unsigned int vector) |
---|
272 | { |
---|
273 | printk("\n---Instruction fetch attempted from $%X, no TLB exists\n",f->EXC_SRR0); |
---|
274 | return -1; |
---|
275 | } |
---|
276 | |
---|
277 | int InstructionFetchException(BSP_Exception_frame *f, unsigned int vector) |
---|
278 | { |
---|
279 | printk("\n---Instruction fetch attempted from $%X, TLB is no-execute\n",f->EXC_SRR0); |
---|
280 | return -1; |
---|
281 | } |
---|