|
1 // Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 // All rights reserved. |
|
3 // This component and the accompanying materials are made available |
|
4 // under the terms of the License "Eclipse Public License v1.0" |
|
5 // which accompanies this distribution, and is available |
|
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 // |
|
8 // Initial Contributors: |
|
9 // Nokia Corporation - initial contribution. |
|
10 // |
|
11 // Contributors: |
|
12 // |
|
13 // Description: |
|
14 // e32test\debug\d_schedhook.cpp |
|
15 // |
|
16 // |
|
17 |
|
18 #include <kernel/kernel.h> |
|
19 #include <kernel/kern_priv.h> |
|
20 #include "platform.h" |
|
21 #include <kernel/cache.h> |
|
22 #include <arm.h> |
|
23 #include "d_schedhook.h" |
|
24 |
|
25 _LIT(KLddName,"D_SCHEDHOOK"); |
|
26 |
|
27 NThread* TestThread; |
|
28 TInt TestCount; |
|
29 |
|
30 const TInt KMajorVersionNumber = 0; |
|
31 const TInt KMinorVersionNumber = 1; |
|
32 const TInt KBuildVersionNumber = 1; |
|
33 |
|
34 class DSchedhookTest; |
|
35 |
|
36 class DSchedhookTestFactory : public DLogicalDevice |
|
37 { |
|
38 public: |
|
39 DSchedhookTestFactory(); |
|
40 virtual TInt Install(); |
|
41 virtual void GetCaps(TDes8& aDes) const; |
|
42 virtual TInt Create(DLogicalChannelBase*& aChannel); |
|
43 }; |
|
44 |
|
45 class DHwExcHandler : public DKernelEventHandler |
|
46 { |
|
47 public: |
|
48 DHwExcHandler() : DKernelEventHandler(HandleEvent, this) {} |
|
49 private: |
|
50 static TUint HandleEvent(TKernelEvent aEvent, TAny* a1, TAny* a2, TAny* aThis); |
|
51 }; |
|
52 |
|
53 class DSchedhookTest : public DLogicalChannelBase |
|
54 { |
|
55 public: |
|
56 virtual ~DSchedhookTest(); |
|
57 protected: |
|
58 virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion& aVer); |
|
59 virtual TInt Request(TInt aFunction, TAny* a1, TAny* a2); |
|
60 public: |
|
61 DHwExcHandler* iHwExcHandler; |
|
62 }; |
|
63 |
|
64 DECLARE_STANDARD_LDD() |
|
65 { |
|
66 return new DSchedhookTestFactory; |
|
67 } |
|
68 |
|
69 // |
|
70 // DSchedhookTestFactory |
|
71 // |
|
72 |
|
73 DSchedhookTestFactory::DSchedhookTestFactory() |
|
74 { |
|
75 iVersion=TVersion(KMajorVersionNumber,KMinorVersionNumber,KBuildVersionNumber); |
|
76 } |
|
77 |
|
78 TInt DSchedhookTestFactory::Create(DLogicalChannelBase*& aChannel) |
|
79 /** |
|
80 Create a new DSchedhookTest on this logical device |
|
81 */ |
|
82 { |
|
83 aChannel=new DSchedhookTest; |
|
84 return aChannel ? KErrNone : KErrNoMemory; |
|
85 } |
|
86 |
|
87 TInt DSchedhookTestFactory::Install() |
|
88 /** |
|
89 Install the LDD - overriding pure virtual |
|
90 */ |
|
91 { |
|
92 return SetName(&KLddName); |
|
93 } |
|
94 |
|
95 void DSchedhookTestFactory::GetCaps(TDes8& aDes) const |
|
96 /** |
|
97 Get capabilities - overriding pure virtual |
|
98 */ |
|
99 { |
|
100 TCapsTestV01 b; |
|
101 b.iVersion=TVersion(KMajorVersionNumber,KMinorVersionNumber,KBuildVersionNumber); |
|
102 Kern::InfoCopy(aDes,(TUint8*)&b,sizeof(b)); |
|
103 } |
|
104 |
|
105 // |
|
106 // DHwExcHandler |
|
107 // |
|
108 |
|
109 /** |
|
110 Handle exceptions on our test thread by suspending it |
|
111 */ |
|
112 TUint DHwExcHandler::HandleEvent(TKernelEvent aEvent, TAny* a1, TAny* /*a2*/, TAny* /*aThis*/) |
|
113 { |
|
114 if (aEvent == EEventHwExc) |
|
115 { |
|
116 if(&Kern::CurrentThread().iNThread!=TestThread) |
|
117 return ERunNext; |
|
118 TArmExcInfo *pR=(TArmExcInfo*)a1; |
|
119 pR->iR15+=4; |
|
120 Kern::ThreadSuspend(Kern::CurrentThread(),1); |
|
121 return (TUint)EExcHandled; |
|
122 } |
|
123 return (TUint)ERunNext; |
|
124 } |
|
125 |
|
126 // |
|
127 // DSchedhookTest |
|
128 // |
|
129 |
|
130 TInt DSchedhookTest::DoCreate(TInt /*aUnit*/, const TDesC8* /*aInfo*/, const TVersion& aVer) |
|
131 /** |
|
132 Create channel |
|
133 */ |
|
134 { |
|
135 if (!Kern::QueryVersionSupported(TVersion(KMajorVersionNumber,KMinorVersionNumber,KBuildVersionNumber),aVer)) |
|
136 return KErrNotSupported; |
|
137 iHwExcHandler = new DHwExcHandler; |
|
138 if (! iHwExcHandler) |
|
139 return KErrNoMemory; |
|
140 return iHwExcHandler->Add(); |
|
141 } |
|
142 |
|
143 DSchedhookTest::~DSchedhookTest() |
|
144 { |
|
145 if (iHwExcHandler) |
|
146 iHwExcHandler->Close(); |
|
147 } |
|
148 |
|
149 |
|
150 |
|
151 // |
|
152 // Scheduler Hook functions |
|
153 // |
|
154 |
|
155 NThread* CurrentThread=NULL; |
|
156 |
|
157 void DisableRescheduleCallback() |
|
158 /** |
|
159 Stops the Scheduler calling us on every context switch |
|
160 */ |
|
161 { |
|
162 // Prevent rescheduling whilst we disable the callback |
|
163 NKern::Lock(); |
|
164 // Disable Callback |
|
165 NKern::SetRescheduleCallback(NULL); |
|
166 // Invalidate CurrentThread |
|
167 CurrentThread = NULL; |
|
168 // Callback now disabled... |
|
169 NKern::Unlock(); |
|
170 } |
|
171 |
|
172 |
|
173 |
|
174 void RemoveSchedulerHooks() |
|
175 /** |
|
176 Removes the patches added to the Scheduler code. |
|
177 */ |
|
178 { |
|
179 // Make sure callback is disabled (required before removing hooks) |
|
180 DisableRescheduleCallback(); |
|
181 // Get range of memory used by hooks |
|
182 TLinAddr start,end; |
|
183 NKern::Lock(); |
|
184 NKern::SchedulerHooks(start,end); |
|
185 NKern::Unlock(); |
|
186 // Free shadow pages which cover hooks |
|
187 TUint32 pageSize=Kern::RoundToPageSize(1); |
|
188 NKern::ThreadEnterCS(); |
|
189 for(TLinAddr a=start; a<end; a+=pageSize) |
|
190 Epoc::FreeShadowPage(a); // Ignore errors because we're trying to clean up anyway |
|
191 NKern::ThreadLeaveCS(); |
|
192 } |
|
193 |
|
194 |
|
195 |
|
196 TInt InsertSchedulerHooks() |
|
197 /** |
|
198 Enables use of the Scheduler callback by using shadow pages to patch the Scheduler code. |
|
199 */ |
|
200 { |
|
201 // Get range of memory used by hooks |
|
202 TLinAddr start=0,end=0; |
|
203 NKern::Lock(); |
|
204 NKern::SchedulerHooks(start,end); |
|
205 NKern::Unlock(); |
|
206 if (start==0 && end==0) return KErrNotSupported; |
|
207 |
|
208 // Create shadow pages for hooks |
|
209 TUint32 pageSize=Kern::RoundToPageSize(1); |
|
210 for(TLinAddr a=start; a<end; a+=pageSize) |
|
211 { |
|
212 NKern::ThreadEnterCS(); |
|
213 TInt r=Epoc::AllocShadowPage(a); |
|
214 NKern::ThreadLeaveCS(); |
|
215 if(r!=KErrNone && r!=KErrAlreadyExists) |
|
216 { |
|
217 RemoveSchedulerHooks(); |
|
218 return r; |
|
219 } |
|
220 } |
|
221 // Put hooks in |
|
222 NKern::Lock(); |
|
223 NKern::InsertSchedulerHooks(); |
|
224 NKern::Unlock(); |
|
225 // Make I and D caches consistant for hook region |
|
226 Cache::IMB_Range(start,end-start); |
|
227 |
|
228 return KErrNone; |
|
229 } |
|
230 |
|
231 |
|
232 |
|
233 void RescheduleTestFunction() |
|
234 /** |
|
235 Test function to be called on each thread reschedule |
|
236 */ |
|
237 { |
|
238 // Count rechedules to the test thread |
|
239 if(CurrentThread==TestThread) |
|
240 ++TestCount; |
|
241 } |
|
242 |
|
243 |
|
244 |
|
245 void RescheduleCallback(NThread* aNThread) |
|
246 /** |
|
247 Main scheduler callback. |
|
248 Called with the Kernel Lock (Preemption Lock) held. |
|
249 */ |
|
250 { |
|
251 #ifndef __SMP__ |
|
252 // The 'CurrentThread' is now unscheduled and has become the 'previous thread' |
|
253 // Set this thread 'UserContextType'... |
|
254 CurrentThread->SetUserContextType(); |
|
255 #endif |
|
256 // Make the newly scheduled thread the CurrentThread |
|
257 CurrentThread = aNThread; |
|
258 // Test function |
|
259 RescheduleTestFunction(); |
|
260 } |
|
261 |
|
262 |
|
263 |
|
264 void RescheduleCallbackFirst(NThread* aNThread) |
|
265 /** |
|
266 Scheduler callback used once for initialisation. |
|
267 Called with the Kernel Lock (Preemption Lock) held. |
|
268 */ |
|
269 { |
|
270 // Initialise CurrentThread |
|
271 CurrentThread = aNThread; |
|
272 // Switch future callbacks to the main RescheduleCallback |
|
273 NKern::SetRescheduleCallback(RescheduleCallback); |
|
274 // Test function |
|
275 RescheduleTestFunction(); |
|
276 } |
|
277 |
|
278 |
|
279 |
|
280 void EnableRescheduleCallback() |
|
281 /** |
|
282 Sets the Scheduler to call us back on every thread reschedule |
|
283 */ |
|
284 { |
|
285 // Reset the User Context Type for all threads, because these values |
|
286 // will be out-of-date. (It is our Rescheduler callback which set's them. |
|
287 // and we're just about enable that.) |
|
288 |
|
289 NKern::ThreadEnterCS(); // Prevent us from dying or suspending whilst holding a DMutex |
|
290 DObjectCon& threads=*Kern::Containers()[EThread]; // Get containing holding threads |
|
291 threads.Wait(); // Obtain the container mutex so the list does get changed under us |
|
292 |
|
293 #ifndef __SMP__ |
|
294 // For each thread... |
|
295 TInt c=threads.Count(); |
|
296 for (TInt i=0; i<c; i++) |
|
297 ((DThread*)threads[i])->iNThread.ResetUserContextType(); |
|
298 #endif |
|
299 |
|
300 threads.Signal(); // Release the container mutex |
|
301 NKern::ThreadLeaveCS(); // End of critical section |
|
302 |
|
303 // Ask for callback |
|
304 NKern::SetRescheduleCallback(RescheduleCallbackFirst); |
|
305 } |
|
306 |
|
307 |
|
308 |
|
309 TInt GetThreadUserContext(NThread* aThread,TArmRegSet& aContext) |
|
310 /** |
|
311 Test function to get a threads User Context |
|
312 */ |
|
313 { |
|
314 // Get the User Context Type which our Reschedule hook set |
|
315 TInt type = aThread->iSpare3; /*iUserContextType*/ |
|
316 |
|
317 // Get the table corresponding to the User Context Type |
|
318 const TArmContextElement* c = NThread::UserContextTables()[type]; |
|
319 |
|
320 // Set the User Context by using the table we got |
|
321 TUint32* sp = (TUint32*)aThread->iSavedSP; |
|
322 TUint32* st = (TUint32*)((TUint32)aThread->iStackBase+(TUint32)aThread->iStackSize); |
|
323 TArmReg* out = (TArmReg*)(&aContext); |
|
324 TArmReg* end = (TArmReg*)(&aContext+1); |
|
325 do |
|
326 { |
|
327 TInt v = c->iValue; |
|
328 TInt t = c->iType; |
|
329 ++c; |
|
330 if(t==TArmContextElement::EOffsetFromSp) |
|
331 v = sp[v]; |
|
332 else if(t==TArmContextElement::EOffsetFromStackTop) |
|
333 v = st[-v]; |
|
334 *out++ = v; |
|
335 } |
|
336 while(out<end); |
|
337 |
|
338 return type; |
|
339 } |
|
340 |
|
341 |
|
342 |
|
343 void DumpContext(TArmRegSet& aContext,TInt aType) |
|
344 { |
|
345 Kern::Printf(" Context type %d",aType); |
|
346 Kern::Printf(" r0 =%08x r1 =%08x r2 =%08x r3 =%08x",aContext.iR0,aContext.iR1,aContext.iR2,aContext.iR3); |
|
347 Kern::Printf(" r4 =%08x r5 =%08x r6 =%08x r7 =%08x",aContext.iR4,aContext.iR5,aContext.iR6,aContext.iR7); |
|
348 Kern::Printf(" r8 =%08x r9 =%08x r10=%08x r11=%08x",aContext.iR8,aContext.iR9,aContext.iR10,aContext.iR11); |
|
349 Kern::Printf(" r12=%08x r13=%08x r14=%08x r15=%08x",aContext.iR12,aContext.iR13,aContext.iR14,aContext.iR15); |
|
350 Kern::Printf(" cpsr=%08x dacr=%08x",aContext.iFlags, aContext.iDacr); |
|
351 } |
|
352 |
|
353 |
|
354 |
|
355 TInt TestGetThreadContext(DThread* aThread,TDes8* aContext) |
|
356 { |
|
357 TArmRegSet context1; |
|
358 TArmRegSet context2; |
|
359 |
|
360 memclr(&context1,sizeof(context1)); |
|
361 memclr(&context2,sizeof(context2)); |
|
362 NKern::Lock(); |
|
363 TUint32 unused; |
|
364 NKern::ThreadGetUserContext(&aThread->iNThread,&context1,unused); |
|
365 TInt r=GetThreadUserContext(&aThread->iNThread,context2); |
|
366 NKern::Unlock(); |
|
367 DumpContext(context1,-1); |
|
368 DumpContext(context2,r); |
|
369 |
|
370 TInt len,maxLen; |
|
371 Kern::KUDesInfo(*aContext,len,maxLen); |
|
372 if(maxLen>KMaxThreadContext) |
|
373 maxLen = KMaxThreadContext; |
|
374 TPtr8 ptr((TUint8*)&context1,maxLen,maxLen); |
|
375 Kern::KUDesPut(*aContext,ptr); |
|
376 |
|
377 for(TUint i=0; i<sizeof(context1); i++) |
|
378 if(((TUint8*)&context1)[i]!=((TUint8*)&context2)[i]) |
|
379 return KErrGeneral; |
|
380 return r; |
|
381 } |
|
382 |
|
383 |
|
384 DThread* ThreadFromId(TUint aId) |
|
385 { |
|
386 // This is risky because the thread could die on us an the DThread* become invalid. |
|
387 // We are relying on this never happening in our test code. |
|
388 NKern::ThreadEnterCS(); // Prevent us from dying or suspending whilst holding a DMutex |
|
389 DObjectCon& threads=*Kern::Containers()[EThread]; // Get containing holding threads |
|
390 threads.Wait(); // Obtain the container mutex so the list does get changed under us |
|
391 DThread* thread = Kern::ThreadFromId(aId); |
|
392 threads.Signal(); // Release the container mutex |
|
393 NKern::ThreadLeaveCS(); // End of critical section |
|
394 return thread; |
|
395 } |
|
396 |
|
397 TInt DSchedhookTest::Request(TInt aFunction, TAny* a1, TAny* a2) |
|
398 /** |
|
399 Handle requests from the test program |
|
400 */ |
|
401 { |
|
402 TInt r=KErrNone; |
|
403 switch (aFunction) |
|
404 { |
|
405 case RSchedhookTest::EInstall: |
|
406 r=InsertSchedulerHooks(); |
|
407 break; |
|
408 |
|
409 case RSchedhookTest::EUninstall: |
|
410 RemoveSchedulerHooks(); |
|
411 return KErrNone; |
|
412 |
|
413 case RSchedhookTest::EInsertHooks: |
|
414 { |
|
415 NKern::Lock(); |
|
416 NKern::InsertSchedulerHooks(); |
|
417 TLinAddr start,end; |
|
418 NKern::SchedulerHooks(start,end); |
|
419 NKern::Unlock(); |
|
420 Cache::IMB_Range(start,end-start); |
|
421 } |
|
422 return KErrNone; |
|
423 |
|
424 case RSchedhookTest::ERemoveHooks: |
|
425 { |
|
426 NKern::Lock(); |
|
427 NKern::SetRescheduleCallback(NULL); |
|
428 NKern::RemoveSchedulerHooks(); |
|
429 TLinAddr start,end; |
|
430 NKern::SchedulerHooks(start,end); |
|
431 NKern::Unlock(); |
|
432 Cache::IMB_Range(start,end-start); |
|
433 } |
|
434 return KErrNone; |
|
435 |
|
436 case RSchedhookTest::EEnableCallback: |
|
437 NKern::Lock(); |
|
438 NKern::SetRescheduleCallback(RescheduleCallbackFirst); |
|
439 NKern::Unlock(); |
|
440 return KErrNone; |
|
441 |
|
442 case RSchedhookTest::EDisableCallback: |
|
443 DisableRescheduleCallback(); |
|
444 return KErrNone; |
|
445 |
|
446 case RSchedhookTest::ESetTestThread: |
|
447 { |
|
448 NKern::ThreadEnterCS(); // Prevent us from dying or suspending whilst holding a DMutex |
|
449 DObjectCon& threads=*Kern::Containers()[EThread]; // Get containing holding threads |
|
450 threads.Wait(); // Obtain the container mutex so the list does get changed under us |
|
451 TestThread = &ThreadFromId((TUint)a1)->iNThread; |
|
452 threads.Signal(); // Release the container mutex |
|
453 NKern::ThreadLeaveCS(); // End of critical section |
|
454 TestCount = 0; |
|
455 } |
|
456 break; |
|
457 |
|
458 case RSchedhookTest::EGetThreadContext: |
|
459 return TestGetThreadContext(ThreadFromId((TUint)a1),(TDes8*)a2); |
|
460 |
|
461 case RSchedhookTest::EGetTestCount: |
|
462 return TestCount; |
|
463 |
|
464 default: |
|
465 r=KErrNotSupported; |
|
466 break; |
|
467 } |
|
468 return r; |
|
469 } |
|
470 |