]>
Commit | Line | Data |
---|---|---|
1c79356b | 1 | /* |
2d21ac55 | 2 | * Copyright (c) 2000-2006 Apple Computer, Inc. All rights reserved. |
1c79356b | 3 | * |
2d21ac55 | 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
1c79356b | 5 | * |
2d21ac55 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
8f6c56a5 | 14 | * |
2d21ac55 A |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
8f6c56a5 A |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
2d21ac55 A |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
8f6c56a5 | 25 | * |
2d21ac55 | 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
1c79356b A |
27 | */ |
28 | /* | |
29 | File: PseudoKernel.c | |
30 | ||
31 | Contains: BlueBox PseudoKernel calls | |
32 | Written by: Mark Gorlinsky | |
33 | Bill Angell | |
34 | ||
35 | Copyright: 1997 by Apple Computer, Inc., all rights reserved | |
36 | ||
37 | */ | |
38 | ||
39 | #include <mach/mach_types.h> | |
2d21ac55 | 40 | #include <mach/mach_host.h> |
1c79356b | 41 | #include <mach/kern_return.h> |
91447636 A |
42 | |
43 | #include <kern/kalloc.h> | |
44 | #include <kern/kern_types.h> | |
1c79356b A |
45 | #include <kern/host.h> |
46 | #include <kern/task.h> | |
47 | #include <kern/thread.h> | |
48 | #include <ppc/PseudoKernel.h> | |
49 | #include <ppc/exception.h> | |
50 | #include <ppc/misc_protos.h> | |
51 | #include <ppc/proc_reg.h> | |
91447636 A |
52 | |
53 | #include <vm/pmap.h> | |
54 | #include <vm/vm_map.h> | |
1c79356b A |
55 | #include <vm/vm_kern.h> |
56 | ||
2d21ac55 A |
57 | extern int is_suser(void); |
58 | extern void tbeproc(void *proc); | |
59 | ||
91447636 | 60 | void bbSetRupt(ReturnHandler *rh, thread_t ct); |
1c79356b A |
61 | |
62 | /* | |
63 | ** Function: NotifyInterruption | |
64 | ** | |
65 | ** Inputs: | |
66 | ** ppcInterrupHandler - interrupt handler to execute | |
67 | ** interruptStatePtr - current interrupt state | |
68 | ** | |
69 | ** Outputs: | |
70 | ** | |
71 | ** Notes: | |
72 | ** | |
73 | */ | |
2d21ac55 A |
74 | kern_return_t |
75 | syscall_notify_interrupt(void) | |
76 | { | |
77 | task_t task; | |
91447636 | 78 | thread_t act, fact; |
1c79356b A |
79 | bbRupt *bbr; |
80 | BTTD_t *bttd; | |
81 | int i; | |
82 | ||
83 | task = current_task(); /* Figure out who our task is */ | |
84 | ||
85 | task_lock(task); /* Lock our task */ | |
86 | ||
91447636 | 87 | fact = (thread_t)task->threads.next; /* Get the first activation on task */ |
2d21ac55 | 88 | act = NULL; /* Pretend we didn't find it yet */ |
1c79356b | 89 | |
55e303ae | 90 | for(i = 0; i < task->thread_count; i++) { /* Scan the whole list */ |
91447636 A |
91 | if(fact->machine.bbDescAddr) { /* Is this a Blue thread? */ |
92 | bttd = (BTTD_t *)(fact->machine.bbDescAddr & -PAGE_SIZE); | |
1c79356b A |
93 | if(bttd->InterruptVector) { /* Is this the Blue interrupt thread? */ |
94 | act = fact; /* Yeah... */ | |
95 | break; /* Found it, Bail the loop... */ | |
96 | } | |
97 | } | |
91447636 | 98 | fact = (thread_t)fact->task_threads.next; /* Go to the next one */ |
1c79356b A |
99 | } |
100 | ||
101 | if(!act) { /* Couldn't find a bluebox */ | |
102 | task_unlock(task); /* Release task lock */ | |
103 | return KERN_FAILURE; /* No tickie, no shirtee... */ | |
104 | } | |
91447636 A |
105 | |
106 | thread_reference(act); | |
1c79356b | 107 | |
1c79356b A |
108 | task_unlock(task); /* Safe to release now */ |
109 | ||
91447636 A |
110 | thread_mtx_lock(act); |
111 | ||
1c79356b A |
112 | /* if the calling thread is the BlueBox thread that handles interrupts |
113 | * we know that we are in the PsuedoKernel and we can short circuit | |
114 | * setting up the asynchronous task by setting a pending interrupt. | |
115 | */ | |
116 | ||
2d21ac55 | 117 | if (act == current_thread()) { |
1c79356b A |
118 | bttd->InterruptControlWord = bttd->InterruptControlWord | |
119 | ((bttd->postIntMask >> kCR2ToBackupShift) & kBackupCR2Mask); | |
120 | ||
91447636 A |
121 | thread_mtx_unlock(act); /* Unlock the activation */ |
122 | thread_deallocate(act); | |
1c79356b A |
123 | return KERN_SUCCESS; |
124 | } | |
125 | ||
91447636 A |
126 | if(act->machine.emPendRupts >= 16) { /* Have we hit the arbitrary maximum? */ |
127 | thread_mtx_unlock(act); /* Unlock the activation */ | |
128 | thread_deallocate(act); | |
1c79356b A |
129 | return KERN_RESOURCE_SHORTAGE; /* Too many pending right now */ |
130 | } | |
131 | ||
132 | if(!(bbr = (bbRupt *)kalloc(sizeof(bbRupt)))) { /* Get a return handler control block */ | |
91447636 A |
133 | thread_mtx_unlock(act); /* Unlock the activation */ |
134 | thread_deallocate(act); | |
1c79356b A |
135 | return KERN_RESOURCE_SHORTAGE; /* No storage... */ |
136 | } | |
137 | ||
91447636 | 138 | (void)hw_atomic_add(&act->machine.emPendRupts, 1); /* Count this 'rupt */ |
1c79356b A |
139 | bbr->rh.handler = bbSetRupt; /* Set interruption routine */ |
140 | ||
141 | bbr->rh.next = act->handlers; /* Put our interrupt at the start of the list */ | |
142 | act->handlers = &bbr->rh; | |
143 | ||
1c79356b | 144 | act_set_apc(act); /* Set an APC AST */ |
1c79356b | 145 | |
91447636 A |
146 | thread_mtx_unlock(act); /* Unlock the activation */ |
147 | thread_deallocate(act); | |
1c79356b A |
148 | return KERN_SUCCESS; /* We're done... */ |
149 | } | |
150 | ||
151 | /* | |
152 | * This guy is fired off asynchronously to actually do the 'rupt. | |
153 | * We will find the user state savearea and modify it. If we can't, | |
154 | * we just leave after releasing our work area | |
155 | */ | |
156 | ||
91447636 | 157 | void bbSetRupt(ReturnHandler *rh, thread_t act) { |
1c79356b | 158 | |
2d21ac55 | 159 | struct savearea *sv; |
1c79356b A |
160 | BTTD_t *bttd; |
161 | bbRupt *bbr; | |
162 | UInt32 interruptState; | |
163 | ||
164 | bbr = (bbRupt *)rh; /* Make our area convenient */ | |
165 | ||
91447636 A |
166 | if(!(act->machine.bbDescAddr)) { /* Is BlueBox still enabled? */ |
167 | kfree(bbr, sizeof(bbRupt)); /* No, release the control block */ | |
1c79356b A |
168 | return; |
169 | } | |
170 | ||
91447636 | 171 | (void)hw_atomic_sub(&act->machine.emPendRupts, 1); /* Uncount this 'rupt */ |
1c79356b | 172 | |
9bccf70c | 173 | if(!(sv = find_user_regs(act))) { /* Find the user state registers */ |
91447636 | 174 | kfree(bbr, sizeof(bbRupt)); /* Couldn't find 'em, release the control block */ |
1c79356b A |
175 | return; |
176 | } | |
177 | ||
91447636 | 178 | bttd = (BTTD_t *)(act->machine.bbDescAddr & -PAGE_SIZE); |
1c79356b A |
179 | |
180 | interruptState = (bttd->InterruptControlWord & kInterruptStateMask) >> kInterruptStateShift; | |
181 | ||
182 | switch (interruptState) { | |
183 | ||
184 | case kInSystemContext: | |
185 | sv->save_cr |= bttd->postIntMask; /* post int in CR2 */ | |
186 | break; | |
187 | ||
188 | case kInAlternateContext: | |
189 | bttd->InterruptControlWord = (bttd->InterruptControlWord & ~kInterruptStateMask) | | |
190 | (kInPseudoKernel << kInterruptStateShift); | |
191 | ||
55e303ae | 192 | bttd->exceptionInfo.srr0 = (unsigned int)sv->save_srr0; /* Save the current PC */ |
91447636 | 193 | sv->save_srr0 = (uint64_t)act->machine.bbInterrupt; /* Set the new PC */ |
55e303ae A |
194 | bttd->exceptionInfo.sprg1 = (unsigned int)sv->save_r1; /* Save the original R1 */ |
195 | sv->save_r1 = (uint64_t)bttd->exceptionInfo.sprg0; /* Set the new R1 */ | |
196 | bttd->exceptionInfo.srr1 = (unsigned int)sv->save_srr1; /* Save the original MSR */ | |
1c79356b | 197 | sv->save_srr1 &= ~(MASK(MSR_BE)|MASK(MSR_SE)); /* Clear SE|BE bits in MSR */ |
91447636 | 198 | act->machine.specFlags &= ~bbNoMachSC; /* reactivate Mach SCs */ |
0b4e3aa0 | 199 | disable_preemption(); /* Don't move us around */ |
91447636 | 200 | getPerProc()->spcFlags = act->machine.specFlags; /* Copy the flags */ |
0b4e3aa0 | 201 | enable_preemption(); /* Ok to move us around */ |
1c79356b A |
202 | /* drop through to post int in backup CR2 in ICW */ |
203 | ||
204 | case kInExceptionHandler: | |
205 | case kInPseudoKernel: | |
206 | case kOutsideBlue: | |
207 | bttd->InterruptControlWord = bttd->InterruptControlWord | | |
208 | ((bttd->postIntMask >> kCR2ToBackupShift) & kBackupCR2Mask); | |
209 | break; | |
210 | ||
211 | default: | |
212 | break; | |
213 | } | |
214 | ||
91447636 | 215 | kfree(bbr, sizeof(bbRupt)); /* Release the control block */ |
1c79356b A |
216 | return; |
217 | ||
218 | } | |
219 | ||
b0d623f7 A |
220 | kern_return_t |
221 | enable_bluebox(host_t host, unsigned _taskID, unsigned _TWI_TableStart, | |
222 | unsigned _Desc_TableStart); | |
223 | kern_return_t disable_bluebox( host_t host ); | |
224 | ||
1c79356b A |
225 | /* |
226 | * This function is used to enable the firmware assist code for bluebox traps, system calls | |
227 | * and interrupts. | |
228 | * | |
229 | * The assist code can be called from two types of threads. The blue thread, which handles | |
230 | * traps, system calls and interrupts and preemptive threads that only issue system calls. | |
231 | * | |
2d21ac55 A |
232 | * Parameters: host . |
233 | * _taskID opaque task ID | |
234 | * _TWI_TableStart Start of TWI table | |
235 | * _Desc_TableStart Start of descriptor table | |
1c79356b A |
236 | */ |
237 | ||
2d21ac55 A |
238 | kern_return_t |
239 | enable_bluebox(host_t host, unsigned _taskID, unsigned _TWI_TableStart, | |
240 | unsigned _Desc_TableStart) | |
241 | { | |
242 | /* XXX mig funness */ | |
243 | void *taskID = (void *)_taskID; | |
244 | void *TWI_TableStart = (void *)_TWI_TableStart; | |
245 | char *Desc_TableStart = (char *)_Desc_TableStart; | |
1c79356b A |
246 | |
247 | thread_t th; | |
55e303ae | 248 | vm_offset_t kerndescaddr, origdescoffset; |
1c79356b | 249 | kern_return_t ret; |
55e303ae A |
250 | ppnum_t physdescpage; |
251 | BTTD_t *bttd; | |
1c79356b A |
252 | |
253 | th = current_thread(); /* Get our thread */ | |
254 | ||
255 | if ( host == HOST_NULL ) return KERN_INVALID_HOST; | |
256 | if ( ! is_suser() ) return KERN_FAILURE; /* We will only do this for the superuser */ | |
91447636 | 257 | if ( th->machine.bbDescAddr ) return KERN_FAILURE; /* Bail if already authorized... */ |
1c79356b A |
258 | if ( ! (unsigned int) Desc_TableStart ) return KERN_FAILURE; /* There has to be a descriptor page */ |
259 | if ( ! TWI_TableStart ) return KERN_FAILURE; /* There has to be a TWI table */ | |
260 | ||
261 | /* Get the page offset of the descriptor */ | |
262 | origdescoffset = (vm_offset_t)Desc_TableStart & (PAGE_SIZE - 1); | |
263 | ||
264 | /* Align the descriptor to a page */ | |
265 | Desc_TableStart = (char *)((vm_offset_t)Desc_TableStart & -PAGE_SIZE); | |
266 | ||
91447636 | 267 | ret = vm_map_wire(th->map, /* Kernel wire the descriptor in the user's map */ |
1c79356b A |
268 | (vm_offset_t)Desc_TableStart, |
269 | (vm_offset_t)Desc_TableStart + PAGE_SIZE, | |
270 | VM_PROT_READ | VM_PROT_WRITE, | |
271 | FALSE); | |
272 | ||
273 | if(ret != KERN_SUCCESS) { /* Couldn't wire it, spit on 'em... */ | |
274 | return KERN_FAILURE; | |
275 | } | |
276 | ||
55e303ae | 277 | physdescpage = /* Get the physical page number of the page */ |
2d21ac55 | 278 | pmap_find_phys(th->map->pmap, CAST_USER_ADDR_T(Desc_TableStart)); |
1c79356b A |
279 | |
280 | ret = kmem_alloc_pageable(kernel_map, &kerndescaddr, PAGE_SIZE); /* Find a virtual address to use */ | |
281 | if(ret != KERN_SUCCESS) { /* Could we get an address? */ | |
91447636 | 282 | (void) vm_map_unwire(th->map, /* No, unwire the descriptor */ |
1c79356b A |
283 | (vm_offset_t)Desc_TableStart, |
284 | (vm_offset_t)Desc_TableStart + PAGE_SIZE, | |
285 | TRUE); | |
286 | return KERN_FAILURE; /* Split... */ | |
287 | } | |
288 | ||
289 | (void) pmap_enter(kernel_pmap, /* Map this into the kernel */ | |
55e303ae | 290 | kerndescaddr, physdescpage, VM_PROT_READ|VM_PROT_WRITE, |
9bccf70c | 291 | VM_WIMG_USE_DEFAULT, TRUE); |
1c79356b | 292 | |
55e303ae A |
293 | bttd = (BTTD_t *)kerndescaddr; /* Get the address in a convienient spot */ |
294 | ||
91447636 A |
295 | th->machine.bbDescAddr = (unsigned int)kerndescaddr+origdescoffset; /* Set kernel address of the table */ |
296 | th->machine.bbUserDA = (unsigned int)Desc_TableStart; /* Set user address of the table */ | |
297 | th->machine.bbTableStart = (unsigned int)TWI_TableStart; /* Set address of the trap table */ | |
298 | th->machine.bbTaskID = (unsigned int)taskID; /* Assign opaque task ID */ | |
299 | th->machine.bbTaskEnv = 0; /* Clean task environment data */ | |
300 | th->machine.emPendRupts = 0; /* Clean pending 'rupt count */ | |
301 | th->machine.bbTrap = bttd->TrapVector; /* Remember trap vector */ | |
302 | th->machine.bbSysCall = bttd->SysCallVector; /* Remember syscall vector */ | |
303 | th->machine.bbInterrupt = bttd->InterruptVector; /* Remember interrupt vector */ | |
304 | th->machine.bbPending = bttd->PendingIntVector; /* Remember pending vector */ | |
305 | th->machine.specFlags &= ~(bbNoMachSC | bbPreemptive); /* Make sure mach SCs are enabled and we are not marked preemptive */ | |
306 | th->machine.specFlags |= bbThread; /* Set that we are Classic thread */ | |
0b4e3aa0 | 307 | |
55e303ae | 308 | if(!(bttd->InterruptVector)) { /* See if this is a preemptive (MP) BlueBox thread */ |
91447636 | 309 | th->machine.specFlags |= bbPreemptive; /* Yes, remember it */ |
0b4e3aa0 A |
310 | } |
311 | ||
312 | disable_preemption(); /* Don't move us around */ | |
91447636 | 313 | getPerProc()->spcFlags = th->machine.specFlags; /* Copy the flags */ |
0b4e3aa0 | 314 | enable_preemption(); /* Ok to move us around */ |
1c79356b A |
315 | |
316 | { | |
317 | /* mark the proc to indicate that this is a TBE proc */ | |
1c79356b | 318 | |
91447636 | 319 | tbeproc(th->task->bsd_info); |
1c79356b A |
320 | } |
321 | ||
322 | return KERN_SUCCESS; | |
323 | } | |
324 | ||
325 | kern_return_t disable_bluebox( host_t host ) { /* User call to terminate bluebox */ | |
326 | ||
91447636 | 327 | thread_t act; |
1c79356b | 328 | |
91447636 | 329 | act = current_thread(); /* Get our thread */ |
1c79356b A |
330 | |
331 | if (host == HOST_NULL) return KERN_INVALID_HOST; | |
332 | ||
333 | if(!is_suser()) return KERN_FAILURE; /* We will only do this for the superuser */ | |
91447636 | 334 | if(!act->machine.bbDescAddr) return KERN_FAILURE; /* Bail if not authorized... */ |
1c79356b A |
335 | |
336 | disable_bluebox_internal(act); /* Clean it all up */ | |
337 | return KERN_SUCCESS; /* Leave */ | |
338 | } | |
339 | ||
91447636 | 340 | void disable_bluebox_internal(thread_t act) { /* Terminate bluebox */ |
1c79356b A |
341 | |
342 | (void) vm_map_unwire(act->map, /* Unwire the descriptor in user's address space */ | |
91447636 A |
343 | (vm_offset_t)act->machine.bbUserDA, |
344 | (vm_offset_t)act->machine.bbUserDA + PAGE_SIZE, | |
1c79356b A |
345 | FALSE); |
346 | ||
91447636 | 347 | kmem_free(kernel_map, (vm_offset_t)act->machine.bbDescAddr & -PAGE_SIZE, PAGE_SIZE); /* Release the page */ |
1c79356b | 348 | |
91447636 A |
349 | act->machine.bbDescAddr = 0; /* Clear kernel pointer to it */ |
350 | act->machine.bbUserDA = 0; /* Clear user pointer to it */ | |
351 | act->machine.bbTableStart = 0; /* Clear user pointer to TWI table */ | |
352 | act->machine.bbTaskID = 0; /* Clear opaque task ID */ | |
353 | act->machine.bbTaskEnv = 0; /* Clean task environment data */ | |
354 | act->machine.emPendRupts = 0; /* Clean pending 'rupt count */ | |
355 | act->machine.specFlags &= ~(bbNoMachSC | bbPreemptive | bbThread); /* Clean up Blue Box enables */ | |
0b4e3aa0 | 356 | disable_preemption(); /* Don't move us around */ |
91447636 | 357 | getPerProc()->spcFlags = act->machine.specFlags; /* Copy the flags */ |
0b4e3aa0 | 358 | enable_preemption(); /* Ok to move us around */ |
1c79356b A |
359 | return; |
360 | } | |
361 | ||
362 | /* | |
363 | * Use the new PPCcall method to enable blue box threads | |
364 | * | |
365 | * save->r3 = taskID | |
366 | * save->r4 = TWI_TableStart | |
367 | * save->r5 = Desc_TableStart | |
368 | * | |
369 | */ | |
370 | int bb_enable_bluebox( struct savearea *save ) | |
371 | { | |
372 | kern_return_t rc; | |
373 | ||
2d21ac55 A |
374 | rc = enable_bluebox((host_t)0xFFFFFFFF, |
375 | CAST_DOWN(unsigned, save->save_r3), | |
376 | CAST_DOWN(unsigned, save->save_r4), | |
377 | CAST_DOWN(unsigned, save->save_r5)); | |
1c79356b A |
378 | save->save_r3 = rc; |
379 | return 1; /* Return with normal AST checking */ | |
380 | } | |
381 | ||
382 | /* | |
383 | * Use the new PPCcall method to disable blue box threads | |
384 | * | |
385 | */ | |
386 | int bb_disable_bluebox( struct savearea *save ) | |
387 | { | |
388 | kern_return_t rc; | |
389 | ||
390 | rc = disable_bluebox( (host_t)0xFFFFFFFF ); | |
391 | save->save_r3 = rc; | |
392 | return 1; /* Return with normal AST checking */ | |
393 | } | |
394 | ||
395 | /* | |
396 | * Search through the list of threads to find the matching taskIDs, then | |
397 | * set the task environment pointer. A task in this case is a preemptive thread | |
398 | * in MacOS 9. | |
399 | * | |
400 | * save->r3 = taskID | |
401 | * save->r4 = taskEnv | |
402 | */ | |
403 | ||
404 | int bb_settaskenv( struct savearea *save ) | |
405 | { | |
406 | int i; | |
407 | task_t task; | |
91447636 | 408 | thread_t act, fact; |
1c79356b A |
409 | |
410 | ||
411 | task = current_task(); /* Figure out who our task is */ | |
412 | ||
413 | task_lock(task); /* Lock our task */ | |
91447636 | 414 | fact = (thread_t)task->threads.next; /* Get the first activation on task */ |
2d21ac55 | 415 | act = NULL; /* Pretend we didn't find it yet */ |
1c79356b | 416 | |
55e303ae | 417 | for(i = 0; i < task->thread_count; i++) { /* Scan the whole list */ |
91447636 A |
418 | if(fact->machine.bbDescAddr) { /* Is this a Blue thread? */ |
419 | if ( fact->machine.bbTaskID == save->save_r3 ) { /* Is this the task we are looking for? */ | |
1c79356b A |
420 | act = fact; /* Yeah... */ |
421 | break; /* Found it, Bail the loop... */ | |
422 | } | |
423 | } | |
91447636 | 424 | fact = (thread_t)fact->task_threads.next; /* Go to the next one */ |
1c79356b A |
425 | } |
426 | ||
427 | if ( !act || !act->active) { | |
428 | task_unlock(task); /* Release task lock */ | |
91447636 A |
429 | save->save_r3 = -1; /* we failed to find the taskID */ |
430 | return 1; | |
1c79356b A |
431 | } |
432 | ||
91447636 A |
433 | thread_reference(act); |
434 | ||
1c79356b A |
435 | task_unlock(task); /* Safe to release now */ |
436 | ||
91447636 A |
437 | thread_mtx_lock(act); /* Make sure this stays 'round */ |
438 | ||
439 | act->machine.bbTaskEnv = save->save_r4; | |
440 | if(act == current_thread()) { /* Are we setting our own? */ | |
0b4e3aa0 | 441 | disable_preemption(); /* Don't move us around */ |
91447636 | 442 | getPerProc()->ppbbTaskEnv = act->machine.bbTaskEnv; /* Remember the environment */ |
0b4e3aa0 A |
443 | enable_preemption(); /* Ok to move us around */ |
444 | } | |
1c79356b | 445 | |
91447636 A |
446 | thread_mtx_unlock(act); /* Unlock the activation */ |
447 | thread_deallocate(act); | |
1c79356b | 448 | save->save_r3 = 0; |
0b4e3aa0 | 449 | return 1; |
1c79356b | 450 | } |