/branches/sparc/kernel/generic/src/main/uinit.c |
---|
46,7 → 46,9 |
#include <userspace.h> |
#include <mm/slab.h> |
#include <arch.h> |
#include <udebug/udebug.h> |
/** Thread used to bring up userspace thread. |
* |
* @param arg Pointer to structure containing userspace entry and stack |
66,6 → 68,10 |
*/ |
thread_detach(THREAD); |
#ifdef CONFIG_UDEBUG |
udebug_stoppable_end(); |
#endif |
uarg.uspace_entry = ((uspace_arg_t *) arg)->uspace_entry; |
uarg.uspace_stack = ((uspace_arg_t *) arg)->uspace_stack; |
uarg.uspace_uarg = ((uspace_arg_t *) arg)->uspace_uarg; |
/branches/sparc/kernel/generic/src/console/cmd.c |
---|
398,17 → 398,17 |
.argc = 0 |
}; |
/* Data and methods for 'ipc_task' command */ |
static int cmd_ipc_task(cmd_arg_t *argv); |
static cmd_arg_t ipc_task_argv = { |
/* Data and methods for 'ipc' command */ |
static int cmd_ipc(cmd_arg_t *argv); |
static cmd_arg_t ipc_argv = { |
.type = ARG_TYPE_INT, |
}; |
static cmd_info_t ipc_task_info = { |
.name = "ipc_task", |
.description = "ipc_task <taskid> Show IPC information of given task.", |
.func = cmd_ipc_task, |
static cmd_info_t ipc_info = { |
.name = "ipc", |
.description = "ipc <taskid> Show IPC information of given task.", |
.func = cmd_ipc, |
.argc = 1, |
.argv = &ipc_task_argv |
.argv = &ipc_argv |
}; |
/* Data and methods for 'zone' command */ |
461,7 → 461,7 |
&uptime_info, |
&halt_info, |
&help_info, |
&ipc_task_info, |
&ipc_info, |
&set4_info, |
&slabs_info, |
&symaddr_info, |
937,7 → 937,7 |
* |
* return Always 1 |
*/ |
int cmd_ipc_task(cmd_arg_t * argv) { |
int cmd_ipc(cmd_arg_t * argv) { |
ipc_print_task(argv[0].intval); |
return 1; |
} |
/branches/sparc/kernel/generic/src/proc/task.c |
---|
156,6 → 156,17 |
ta->capabilities = 0; |
ta->cycles = 0; |
#ifdef CONFIG_UDEBUG |
/* Init debugging stuff */ |
udebug_task_init(&ta->udebug); |
/* Init kbox stuff */ |
ipc_answerbox_init(&ta->kernel_box, ta); |
ta->kb_thread = NULL; |
mutex_initialize(&ta->kb_cleanup_lock, MUTEX_PASSIVE); |
ta->kb_finished = false; |
#endif |
ipc_answerbox_init(&ta->answerbox, ta); |
for (i = 0; i < IPC_MAX_PHONES; i++) |
ipc_phone_init(&ta->phones[i]); |
/branches/sparc/kernel/generic/src/proc/thread.c |
---|
180,6 → 180,10 |
return -1; |
} |
#ifdef CONFIG_UDEBUG |
mutex_initialize(&t->udebug.lock, MUTEX_PASSIVE); |
#endif |
return 0; |
} |
347,6 → 351,11 |
avltree_node_initialize(&t->threads_tree_node); |
t->threads_tree_node.key = (uintptr_t) t; |
#ifdef CONFIG_UDEBUG |
/* Init debugging stuff */ |
udebug_thread_initialize(&t->udebug); |
#endif |
/* might depend on previous initialization */ |
thread_create_arch(t); |
409,12 → 418,17 |
ipl_t ipl; |
/* |
* Attach to the current task. |
* Attach to the specified task. |
*/ |
ipl = interrupts_disable(); |
spinlock_lock(&task->lock); |
atomic_inc(&task->refcount); |
/* Must not count kbox thread into lifecount */ |
if (t->flags & THREAD_FLAG_USPACE) |
atomic_inc(&task->lifecount); |
list_append(&t->th_link, &task->th_head); |
spinlock_unlock(&task->lock); |
437,14 → 451,19 |
{ |
ipl_t ipl; |
if (THREAD->flags & THREAD_FLAG_USPACE) { |
#ifdef CONFIG_UDEBUG |
/* Generate udebug THREAD_E event */ |
udebug_thread_e_event(); |
#endif |
if (atomic_predec(&TASK->lifecount) == 0) { |
/* |
* We are the last thread in the task that still has not exited. |
* With the exception of the moment the task was created, new |
* threads can only be created by threads of the same task. |
* We are the last userspace thread in the task that |
* still has not exited. With the exception of the |
* moment the task was created, new userspace threads |
* can only be created by threads of the same task. |
* We are safe to perform cleanup. |
*/ |
if (THREAD->flags & THREAD_FLAG_USPACE) { |
ipc_cleanup(); |
futex_cleanup(); |
LOG("Cleanup of task %" PRIu64" completed.", TASK->taskid); |
741,6 → 760,11 |
thread_attach(t, TASK); |
thread_ready(t); |
#ifdef CONFIG_UDEBUG |
/* Generate udebug THREAD_B event */ |
udebug_thread_b_event(t); |
#endif |
return 0; |
} else |
free(kernel_uarg); |
/branches/sparc/kernel/generic/src/lib/memstr.c |
---|
34,12 → 34,10 |
* @file |
* @brief Memory string operations. |
* |
* This file provides architecture independent functions |
* to manipulate blocks of memory. These functions |
* are optimized as much as generic functions of |
* this type can be. However, architectures are |
* free to provide even more optimized versions of these |
* functions. |
* This file provides architecture independent functions to manipulate blocks of |
* memory. These functions are optimized as much as generic functions of this |
* type can be. However, architectures are free to provide even more optimized |
* versions of these functions. |
*/ |
#include <memstr.h> |
46,16 → 44,17 |
#include <arch/types.h> |
#include <align.h> |
/** Copy block of memory |
/** Copy block of memory. |
* |
* Copy cnt bytes from src address to dst address. |
* The copying is done word-by-word and then byte-by-byte. |
* The source and destination memory areas cannot overlap. |
* Copy cnt bytes from src address to dst address. The copying is done |
* word-by-word and then byte-by-byte. The source and destination memory areas |
* cannot overlap. |
* |
* @param src Origin address to copy from. |
* @param dst Origin address to copy to. |
* @param src Source address to copy from. |
* @param dst Destination address to copy to. |
* @param cnt Number of bytes to copy. |
* |
* @return Destination address. |
*/ |
void *_memcpy(void * dst, const void *src, size_t cnt) |
{ |
66,12 → 65,12 |
for (i = 0; i < cnt; i++) |
((uint8_t *) dst)[i] = ((uint8_t *) src)[i]; |
} else { |
for (i = 0; i < cnt / sizeof(unative_t); i++) |
((unative_t *) dst)[i] = ((unative_t *) src)[i]; |
for (j = 0; j < cnt % sizeof(unative_t); j++) |
((uint8_t *)(((unative_t *) dst) + i))[j] = ((uint8_t *)(((unative_t *) src) + i))[j]; |
((uint8_t *)(((unative_t *) dst) + i))[j] = |
((uint8_t *)(((unative_t *) src) + i))[j]; |
} |
return (char *) dst; |
79,10 → 78,10 |
/** Fill block of memory |
* |
* Fill cnt bytes at dst address with the value x. |
* The filling is done byte-by-byte. |
* Fill cnt bytes at dst address with the value x. The filling is done |
* byte-by-byte. |
* |
* @param dst Origin address to fill. |
* @param dst Destination address to fill. |
* @param cnt Number of bytes to fill. |
* @param x Value to fill. |
* |
96,12 → 95,12 |
p[i] = x; |
} |
/** Fill block of memory |
/** Fill block of memory. |
* |
* Fill cnt words at dst address with the value x. |
* The filling is done word-by-word. |
* Fill cnt words at dst address with the value x. The filling is done |
* word-by-word. |
* |
* @param dst Origin address to fill. |
* @param dst Destination address to fill. |
* @param cnt Number of words to fill. |
* @param x Value to fill. |
* |
115,16 → 114,16 |
p[i] = x; |
} |
/** Copy string |
/** Copy string. |
* |
* Copy string from src address to dst address. |
* The copying is done char-by-char until the null |
* character. The source and destination memory areas |
* cannot overlap. |
* Copy string from src address to dst address. The copying is done |
* char-by-char until the null character. The source and destination memory |
* areas cannot overlap. |
* |
* @param src Origin string to copy from. |
* @param dst Origin string to copy to. |
* @param src Source string to copy from. |
* @param dst Destination string to copy to. |
* |
* @return Address of the destination string. |
*/ |
char *strcpy(char *dest, const char *src) |
{ |
/branches/sparc/kernel/generic/src/mm/as.c |
---|
395,7 → 395,7 |
/* |
* Start TLB shootdown sequence. |
*/ |
tlb_shootdown_start(TLB_INVL_PAGES, AS->asid, area->base + |
tlb_shootdown_start(TLB_INVL_PAGES, as->asid, area->base + |
pages * PAGE_SIZE, area->pages - pages); |
/* |
/branches/sparc/kernel/generic/src/syscall/syscall.c |
---|
53,6 → 53,7 |
#include <syscall/copy.h> |
#include <sysinfo/sysinfo.h> |
#include <console/console.h> |
#include <udebug/udebug.h> |
/** Print using kernel facility |
* |
101,9 → 102,16 |
{ |
unative_t rc; |
if (id < SYSCALL_END) |
#ifdef CONFIG_UDEBUG |
udebug_syscall_event(a1, a2, a3, a4, a5, a6, id, 0, false); |
#endif |
if (id < SYSCALL_END) { |
#ifdef CONFIG_UDEBUG |
udebug_stoppable_begin(); |
#endif |
rc = syscall_table[id](a1, a2, a3, a4, a5, a6); |
else { |
} else { |
printf("Task %" PRIu64": Unknown syscall %#" PRIxn, TASK->taskid, id); |
task_kill(TASK->taskid); |
thread_exit(); |
112,6 → 120,11 |
if (THREAD->interrupted) |
thread_exit(); |
#ifdef CONFIG_UDEBUG |
udebug_syscall_event(a1, a2, a3, a4, a5, a6, id, rc, true); |
udebug_stoppable_end(); |
#endif |
return rc; |
} |
165,7 → 178,9 |
(syshandler_t) sys_sysinfo_value, |
/* Debug calls */ |
(syshandler_t) sys_debug_enable_console |
(syshandler_t) sys_debug_enable_console, |
(syshandler_t) sys_ipc_connect_kbox |
}; |
/** @} |
/branches/sparc/kernel/generic/src/ipc/kbox.c |
---|
0,0 → 1,220 |
/* |
* Copyright (c) 2008 Jiri Svoboda |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* |
* - Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* - 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. |
* - 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 ``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 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. |
*/ |
/** @addtogroup genericipc |
* @{ |
*/ |
/** @file |
*/ |
#include <synch/synch.h> |
#include <synch/spinlock.h> |
#include <synch/mutex.h> |
#include <ipc/ipc.h> |
#include <ipc/ipcrsc.h> |
#include <arch.h> |
#include <errno.h> |
#include <debug.h> |
#include <udebug/udebug_ipc.h> |
#include <ipc/kbox.h> |
void ipc_kbox_cleanup(void) |
{ |
bool have_kb_thread; |
/* Only hold kb_cleanup_lock while setting kb_finished - this is enough */ |
mutex_lock(&TASK->kb_cleanup_lock); |
TASK->kb_finished = true; |
mutex_unlock(&TASK->kb_cleanup_lock); |
have_kb_thread = (TASK->kb_thread != NULL); |
/* From now on nobody will try to connect phones or attach kbox threads */ |
/* |
* Disconnect all phones connected to our kbox. Passing true for |
* notify_box causes a HANGUP message to be inserted for each |
* disconnected phone. This ensures the kbox thread is going to |
* wake up and terminate. |
*/ |
ipc_answerbox_slam_phones(&TASK->kernel_box, have_kb_thread); |
if (have_kb_thread) { |
LOG("join kb_thread..\n"); |
thread_join(TASK->kb_thread); |
thread_detach(TASK->kb_thread); |
LOG("join done\n"); |
TASK->kb_thread = NULL; |
} |
/* Answer all messages in 'calls' and 'dispatched_calls' queues */ |
spinlock_lock(&TASK->kernel_box.lock); |
ipc_cleanup_call_list(&TASK->kernel_box.dispatched_calls); |
ipc_cleanup_call_list(&TASK->kernel_box.calls); |
spinlock_unlock(&TASK->kernel_box.lock); |
} |
static void kbox_thread_proc(void *arg) |
{ |
call_t *call; |
int method; |
bool done; |
ipl_t ipl; |
(void)arg; |
LOG("kbox_thread_proc()\n"); |
done = false; |
while (!done) { |
call = ipc_wait_for_call(&TASK->kernel_box, SYNCH_NO_TIMEOUT, |
SYNCH_FLAGS_NONE); |
if (call != NULL) { |
method = IPC_GET_METHOD(call->data); |
if (method == IPC_M_DEBUG_ALL) { |
udebug_call_receive(call); |
} |
if (method == IPC_M_PHONE_HUNGUP) { |
LOG("kbox: handle hangup message\n"); |
/* Was it our debugger, who hung up? */ |
if (call->sender == TASK->udebug.debugger) { |
/* Terminate debugging session (if any) */ |
LOG("kbox: terminate debug session\n"); |
ipl = interrupts_disable(); |
spinlock_lock(&TASK->lock); |
udebug_task_cleanup(TASK); |
spinlock_unlock(&TASK->lock); |
interrupts_restore(ipl); |
} else { |
LOG("kbox: was not debugger\n"); |
} |
LOG("kbox: continue with hangup message\n"); |
IPC_SET_RETVAL(call->data, 0); |
ipc_answer(&TASK->kernel_box, call); |
ipl = interrupts_disable(); |
spinlock_lock(&TASK->lock); |
spinlock_lock(&TASK->answerbox.lock); |
if (list_empty(&TASK->answerbox.connected_phones)) { |
/* Last phone has been disconnected */ |
TASK->kb_thread = NULL; |
done = true; |
LOG("phone list is empty\n"); |
} |
spinlock_unlock(&TASK->answerbox.lock); |
spinlock_unlock(&TASK->lock); |
interrupts_restore(ipl); |
} |
} |
} |
LOG("kbox: finished\n"); |
} |
/** |
* Connect phone to a task kernel-box specified by id. |
* |
* Note that this is not completely atomic. For optimisation reasons, |
* The task might start cleaning up kbox after the phone has been connected |
* and before a kbox thread has been created. This must be taken into account |
* in the cleanup code. |
* |
* @return Phone id on success, or negative error code. |
*/ |
int ipc_connect_kbox(task_id_t taskid) |
{ |
int newphid; |
task_t *ta; |
thread_t *kb_thread; |
ipl_t ipl; |
ipl = interrupts_disable(); |
spinlock_lock(&tasks_lock); |
ta = task_find_by_id(taskid); |
if (ta == NULL) { |
spinlock_unlock(&tasks_lock); |
interrupts_restore(ipl); |
return ENOENT; |
} |
atomic_inc(&ta->refcount); |
spinlock_unlock(&tasks_lock); |
interrupts_restore(ipl); |
mutex_lock(&ta->kb_cleanup_lock); |
if (atomic_predec(&ta->refcount) == 0) { |
mutex_unlock(&ta->kb_cleanup_lock); |
task_destroy(ta); |
return ENOENT; |
} |
if (ta->kb_finished != false) { |
mutex_unlock(&ta->kb_cleanup_lock); |
return EINVAL; |
} |
newphid = phone_alloc(); |
if (newphid < 0) { |
mutex_unlock(&ta->kb_cleanup_lock); |
return ELIMIT; |
} |
/* Connect the newly allocated phone to the kbox */ |
ipc_phone_connect(&TASK->phones[newphid], &ta->kernel_box); |
if (ta->kb_thread != NULL) { |
mutex_unlock(&ta->kb_cleanup_lock); |
return newphid; |
} |
/* Create a kbox thread */ |
kb_thread = thread_create(kbox_thread_proc, NULL, ta, 0, "kbox", false); |
if (!kb_thread) { |
mutex_unlock(&ta->kb_cleanup_lock); |
return ENOMEM; |
} |
ta->kb_thread = kb_thread; |
thread_ready(kb_thread); |
mutex_unlock(&ta->kb_cleanup_lock); |
return newphid; |
} |
/** @} |
*/ |
/branches/sparc/kernel/generic/src/ipc/sysipc.c |
---|
42,8 → 42,9 |
#include <ipc/sysipc.h> |
#include <ipc/irq.h> |
#include <ipc/ipcrsc.h> |
#include <ipc/kbox.h> |
#include <udebug/udebug_ipc.h> |
#include <arch/interrupt.h> |
#include <print.h> |
#include <syscall/copy.h> |
#include <security/cap.h> |
#include <mm/as.h> |
295,10 → 296,11 |
/** Called before the request is sent. |
* |
* @param call Call structure with the request. |
* @param phone Phone that the call will be sent through. |
* |
* @return Return 0 on success, ELIMIT or EPERM on error. |
*/ |
static int request_preprocess(call_t *call) |
static int request_preprocess(call_t *call, phone_t *phone) |
{ |
int newphid; |
size_t size; |
340,6 → 342,10 |
return rc; |
} |
break; |
#ifdef CONFIG_UDEBUG |
case IPC_M_DEBUG_ALL: |
return udebug_request_preprocess(call, phone); |
#endif |
default: |
break; |
} |
369,6 → 375,7 |
if (call->buffer) { |
/* This must be an affirmative answer to IPC_M_DATA_READ. */ |
/* or IPC_M_DEBUG_ALL/UDEBUG_M_MEM_READ... */ |
uintptr_t dst = IPC_GET_ARG1(call->data); |
size_t size = IPC_GET_ARG2(call->data); |
int rc = copy_to_uspace((void *) dst, call->buffer, size); |
400,6 → 407,12 |
} |
IPC_SET_ARG5(call->data, phoneid); |
} |
switch (IPC_GET_METHOD(call->data)) { |
case IPC_M_DEBUG_ALL: |
return -1; |
default: |
break; |
} |
return 0; |
} |
441,7 → 454,7 |
IPC_SET_ARG4(call.data, 0); |
IPC_SET_ARG5(call.data, 0); |
if (!(res = request_preprocess(&call))) { |
if (!(res = request_preprocess(&call, phone))) { |
rc = ipc_call_sync(phone, &call); |
if (rc != EOK) |
return rc; |
481,7 → 494,7 |
GET_CHECK_PHONE(phone, phoneid, return ENOENT); |
if (!(res = request_preprocess(&call))) { |
if (!(res = request_preprocess(&call, phone))) { |
rc = ipc_call_sync(phone, &call); |
if (rc != EOK) |
return rc; |
550,7 → 563,7 |
*/ |
IPC_SET_ARG5(call->data, 0); |
if (!(res = request_preprocess(call))) |
if (!(res = request_preprocess(call, phone))) |
ipc_call(phone, call); |
else |
ipc_backsend_err(phone, call, res); |
584,7 → 597,7 |
ipc_call_free(call); |
return (unative_t) rc; |
} |
if (!(res = request_preprocess(call))) |
if (!(res = request_preprocess(call, phone))) |
ipc_call(phone, call); |
else |
ipc_backsend_err(phone, call, res); |
809,11 → 822,16 |
ASSERT(! (call->flags & IPC_CALL_STATIC_ALLOC)); |
atomic_dec(&TASK->active_calls); |
if (call->flags & IPC_CALL_DISCARD_ANSWER) { |
ipc_call_free(call); |
goto restart; |
} else { |
/* |
* Decrement the counter of active calls only if the |
* call is not an answer to IPC_M_PHONE_HUNGUP, |
* which doesn't contribute to the counter. |
*/ |
atomic_dec(&TASK->active_calls); |
} |
STRUCT_TO_USPACE(&calldata->args, &call->data.args); |
868,5 → 886,30 |
return 0; |
} |
#include <console/console.h> |
/** |
* Syscall connect to a task by id. |
* |
* @return Phone id on success, or negative error code. |
*/ |
unative_t sys_ipc_connect_kbox(sysarg64_t *uspace_taskid_arg) |
{ |
#ifdef CONFIG_UDEBUG |
sysarg64_t taskid_arg; |
int rc; |
rc = copy_from_uspace(&taskid_arg, uspace_taskid_arg, sizeof(sysarg64_t)); |
if (rc != 0) |
return (unative_t) rc; |
LOG("sys_ipc_connect_kbox(%" PRIu64 ")\n", taskid_arg.value); |
return ipc_connect_kbox(taskid_arg.value); |
#else |
return (unative_t) ENOTSUP; |
#endif |
} |
/** @} |
*/ |
/branches/sparc/kernel/generic/src/ipc/ipc.c |
---|
43,6 → 43,7 |
#include <synch/waitq.h> |
#include <synch/synch.h> |
#include <ipc/ipc.h> |
#include <ipc/kbox.h> |
#include <errno.h> |
#include <mm/slab.h> |
#include <arch.h> |
51,6 → 52,7 |
#include <debug.h> |
#include <print.h> |
#include <console/console.h> |
#include <proc/thread.h> |
#include <arch/interrupt.h> |
#include <ipc/irq.h> |
427,7 → 429,7 |
* |
* @param lst Head of the list to be cleaned up. |
*/ |
static void ipc_cleanup_call_list(link_t *lst) |
void ipc_cleanup_call_list(link_t *lst) |
{ |
call_t *call; |
442,33 → 444,31 |
} |
} |
/** Cleans up all IPC communication of the current task. |
/** Disconnects all phones connected to an answerbox. |
* |
* Note: ipc_hangup sets returning answerbox to TASK->answerbox, you |
* have to change it as well if you want to cleanup other tasks than TASK. |
* @param box Answerbox to disconnect phones from. |
* @param notify_box If true, the answerbox will get a hangup message for |
* each disconnected phone. |
*/ |
void ipc_cleanup(void) |
void ipc_answerbox_slam_phones(answerbox_t *box, bool notify_box) |
{ |
int i; |
call_t *call; |
phone_t *phone; |
DEADLOCK_PROBE_INIT(p_phonelck); |
ipl_t ipl; |
call_t *call; |
/* Disconnect all our phones ('ipc_phone_hangup') */ |
for (i = 0; i < IPC_MAX_PHONES; i++) |
ipc_phone_hangup(&TASK->phones[i]); |
call = notify_box ? ipc_call_alloc(0) : NULL; |
/* Disconnect all connected irqs */ |
ipc_irq_cleanup(&TASK->answerbox); |
/* Disconnect all phones connected to our answerbox */ |
restart_phones: |
spinlock_lock(&TASK->answerbox.lock); |
while (!list_empty(&TASK->answerbox.connected_phones)) { |
phone = list_get_instance(TASK->answerbox.connected_phones.next, |
ipl = interrupts_disable(); |
spinlock_lock(&box->lock); |
while (!list_empty(&box->connected_phones)) { |
phone = list_get_instance(box->connected_phones.next, |
phone_t, link); |
if (SYNCH_FAILED(mutex_trylock(&phone->lock))) { |
spinlock_unlock(&TASK->answerbox.lock); |
spinlock_unlock(&box->lock); |
interrupts_restore(ipl); |
DEADLOCK_PROBE(p_phonelck, DEADLOCK_THRESHOLD); |
goto restart_phones; |
} |
475,13 → 475,70 |
/* Disconnect phone */ |
ASSERT(phone->state == IPC_PHONE_CONNECTED); |
list_remove(&phone->link); |
phone->state = IPC_PHONE_SLAMMED; |
list_remove(&phone->link); |
if (notify_box) { |
mutex_unlock(&phone->lock); |
spinlock_unlock(&box->lock); |
interrupts_restore(ipl); |
/* |
* Send one message to the answerbox for each |
* phone. Used to make sure the kbox thread |
* wakes up after the last phone has been |
* disconnected. |
*/ |
IPC_SET_METHOD(call->data, IPC_M_PHONE_HUNGUP); |
call->flags |= IPC_CALL_DISCARD_ANSWER; |
_ipc_call(phone, box, call); |
/* Allocate another call in advance */ |
call = ipc_call_alloc(0); |
/* Must start again */ |
goto restart_phones; |
} |
mutex_unlock(&phone->lock); |
} |
spinlock_unlock(&box->lock); |
interrupts_restore(ipl); |
/* Free unused call */ |
if (call) |
ipc_call_free(call); |
} |
/** Cleans up all IPC communication of the current task. |
* |
* Note: ipc_hangup sets returning answerbox to TASK->answerbox, you |
* have to change it as well if you want to cleanup other tasks than TASK. |
*/ |
void ipc_cleanup(void) |
{ |
int i; |
call_t *call; |
/* Disconnect all our phones ('ipc_phone_hangup') */ |
for (i = 0; i < IPC_MAX_PHONES; i++) |
ipc_phone_hangup(&TASK->phones[i]); |
/* Disconnect all connected irqs */ |
ipc_irq_cleanup(&TASK->answerbox); |
/* Disconnect all phones connected to our regular answerbox */ |
ipc_answerbox_slam_phones(&TASK->answerbox, false); |
#ifdef CONFIG_UDEBUG |
/* Clean up kbox thread and communications */ |
ipc_kbox_cleanup(); |
#endif |
/* Answer all messages in 'calls' and 'dispatched_calls' queues */ |
spinlock_lock(&TASK->answerbox.lock); |
ipc_cleanup_call_list(&TASK->answerbox.dispatched_calls); |
ipc_cleanup_call_list(&TASK->answerbox.calls); |
spinlock_unlock(&TASK->answerbox.lock); |
517,6 → 574,12 |
(call->flags & IPC_CALL_NOTIF)); |
ASSERT(!(call->flags & IPC_CALL_STATIC_ALLOC)); |
/* |
* Record the receipt of this call in the current task's counter |
* of active calls. IPC_M_PHONE_HUNGUP calls do not contribute |
* to this counter so do not record answers to them either. |
*/ |
if (!(call->flags & IPC_CALL_DISCARD_ANSWER)) |
atomic_dec(&TASK->active_calls); |
ipc_call_free(call); |
} |
/branches/sparc/kernel/generic/src/udebug/udebug_ipc.c |
---|
0,0 → 1,343 |
/* |
* Copyright (c) 2008 Jiri Svoboda |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* |
* - Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* - 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. |
* - 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 ``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 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. |
*/ |
/** @addtogroup generic |
* @{ |
*/ |
/** |
* @file |
* @brief Udebug IPC message handling. |
* |
* This module handles udebug IPC messages and calls the appropriate |
* functions from the udebug_ops module which implement them. |
*/ |
#include <proc/task.h> |
#include <proc/thread.h> |
#include <arch.h> |
#include <errno.h> |
#include <ipc/ipc.h> |
#include <syscall/copy.h> |
#include <udebug/udebug.h> |
#include <udebug/udebug_ops.h> |
#include <udebug/udebug_ipc.h> |
int udebug_request_preprocess(call_t *call, phone_t *phone) |
{ |
switch (IPC_GET_ARG1(call->data)) { |
/* future UDEBUG_M_REGS_WRITE, UDEBUG_M_MEM_WRITE: */ |
default: |
break; |
} |
return 0; |
} |
/** Process a BEGIN call. |
* |
* Initiates a debugging session for the current task. The reply |
* to this call may or may not be sent before this function returns. |
* |
* @param call The call structure. |
*/ |
static void udebug_receive_begin(call_t *call) |
{ |
int rc; |
rc = udebug_begin(call); |
if (rc < 0) { |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
return; |
} |
/* |
* If the initialization of the debugging session has finished, |
* send a reply. |
*/ |
if (rc != 0) { |
IPC_SET_RETVAL(call->data, 0); |
ipc_answer(&TASK->kernel_box, call); |
} |
} |
/** Process an END call. |
* |
* Terminates the debugging session for the current task. |
* @param call The call structure. |
*/ |
static void udebug_receive_end(call_t *call) |
{ |
int rc; |
rc = udebug_end(); |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
} |
/** Process a SET_EVMASK call. |
* |
* Sets an event mask for the current debugging session. |
* @param call The call structure. |
*/ |
static void udebug_receive_set_evmask(call_t *call) |
{ |
int rc; |
udebug_evmask_t mask; |
mask = IPC_GET_ARG2(call->data); |
rc = udebug_set_evmask(mask); |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
} |
/** Process a GO call. |
* |
* Resumes execution of the specified thread. |
* @param call The call structure. |
*/ |
static void udebug_receive_go(call_t *call) |
{ |
thread_t *t; |
int rc; |
t = (thread_t *)IPC_GET_ARG2(call->data); |
rc = udebug_go(t, call); |
if (rc < 0) { |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
return; |
} |
} |
/** Process a STOP call. |
* |
* Suspends execution of the specified thread. |
* @param call The call structure. |
*/ |
static void udebug_receive_stop(call_t *call) |
{ |
thread_t *t; |
int rc; |
t = (thread_t *)IPC_GET_ARG2(call->data); |
rc = udebug_stop(t, call); |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
} |
/** Process a THREAD_READ call. |
* |
* Reads the list of hashes of the (userspace) threads in the current task. |
* @param call The call structure. |
*/ |
static void udebug_receive_thread_read(call_t *call) |
{ |
unative_t uspace_addr; |
unative_t to_copy; |
unsigned total_bytes; |
unsigned buf_size; |
void *buffer; |
size_t n; |
int rc; |
uspace_addr = IPC_GET_ARG2(call->data); /* Destination address */ |
buf_size = IPC_GET_ARG3(call->data); /* Dest. buffer size */ |
/* |
* Read thread list. Variable n will be filled with actual number |
* of threads times thread-id size. |
*/ |
rc = udebug_thread_read(&buffer, buf_size, &n); |
if (rc < 0) { |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
return; |
} |
total_bytes = n; |
/* Copy MAX(buf_size, total_bytes) bytes */ |
if (buf_size > total_bytes) |
to_copy = total_bytes; |
else |
to_copy = buf_size; |
/* |
* Make use of call->buffer to transfer data to caller's userspace |
*/ |
IPC_SET_RETVAL(call->data, 0); |
/* ARG1=dest, ARG2=size as in IPC_M_DATA_READ so that |
same code in process_answer() can be used |
(no way to distinguish method in answer) */ |
IPC_SET_ARG1(call->data, uspace_addr); |
IPC_SET_ARG2(call->data, to_copy); |
IPC_SET_ARG3(call->data, total_bytes); |
call->buffer = buffer; |
ipc_answer(&TASK->kernel_box, call); |
} |
/** Process an ARGS_READ call. |
* |
* Reads the argument of a current syscall event (SYSCALL_B or SYSCALL_E). |
* @param call The call structure. |
*/ |
static void udebug_receive_args_read(call_t *call) |
{ |
thread_t *t; |
unative_t uspace_addr; |
int rc; |
void *buffer; |
t = (thread_t *)IPC_GET_ARG2(call->data); |
rc = udebug_args_read(t, &buffer); |
if (rc != EOK) { |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
return; |
} |
/* |
* Make use of call->buffer to transfer data to caller's userspace |
*/ |
uspace_addr = IPC_GET_ARG3(call->data); |
IPC_SET_RETVAL(call->data, 0); |
/* ARG1=dest, ARG2=size as in IPC_M_DATA_READ so that |
same code in process_answer() can be used |
(no way to distinguish method in answer) */ |
IPC_SET_ARG1(call->data, uspace_addr); |
IPC_SET_ARG2(call->data, 6 * sizeof(unative_t)); |
call->buffer = buffer; |
ipc_answer(&TASK->kernel_box, call); |
} |
/** Process an MEM_READ call. |
* |
* Reads memory of the current (debugged) task. |
* @param call The call structure. |
*/ |
static void udebug_receive_mem_read(call_t *call) |
{ |
unative_t uspace_dst; |
unative_t uspace_src; |
unsigned size; |
void *buffer; |
int rc; |
uspace_dst = IPC_GET_ARG2(call->data); |
uspace_src = IPC_GET_ARG3(call->data); |
size = IPC_GET_ARG4(call->data); |
rc = udebug_mem_read(uspace_src, size, &buffer); |
if (rc < 0) { |
IPC_SET_RETVAL(call->data, rc); |
ipc_answer(&TASK->kernel_box, call); |
return; |
} |
IPC_SET_RETVAL(call->data, 0); |
/* ARG1=dest, ARG2=size as in IPC_M_DATA_READ so that |
same code in process_answer() can be used |
(no way to distinguish method in answer) */ |
IPC_SET_ARG1(call->data, uspace_dst); |
IPC_SET_ARG2(call->data, size); |
call->buffer = buffer; |
ipc_answer(&TASK->kernel_box, call); |
} |
/** Handle a debug call received on the kernel answerbox. |
* |
* This is called by the kbox servicing thread. Verifies that the sender |
* is indeed the debugger and calls the appropriate processing function. |
*/ |
void udebug_call_receive(call_t *call) |
{ |
int debug_method; |
debug_method = IPC_GET_ARG1(call->data); |
if (debug_method != UDEBUG_M_BEGIN) { |
/* |
* Verify that the sender is this task's debugger. |
* Note that this is the only thread that could change |
* TASK->debugger. Therefore no locking is necessary |
* and the sender can be safely considered valid until |
* control exits this function. |
*/ |
if (TASK->udebug.debugger != call->sender) { |
IPC_SET_RETVAL(call->data, EINVAL); |
ipc_answer(&TASK->kernel_box, call); |
return; |
} |
} |
switch (debug_method) { |
case UDEBUG_M_BEGIN: |
udebug_receive_begin(call); |
break; |
case UDEBUG_M_END: |
udebug_receive_end(call); |
break; |
case UDEBUG_M_SET_EVMASK: |
udebug_receive_set_evmask(call); |
break; |
case UDEBUG_M_GO: |
udebug_receive_go(call); |
break; |
case UDEBUG_M_STOP: |
udebug_receive_stop(call); |
break; |
case UDEBUG_M_THREAD_READ: |
udebug_receive_thread_read(call); |
break; |
case UDEBUG_M_ARGS_READ: |
udebug_receive_args_read(call); |
break; |
case UDEBUG_M_MEM_READ: |
udebug_receive_mem_read(call); |
break; |
} |
} |
/** @} |
*/ |
/branches/sparc/kernel/generic/src/udebug/udebug.c |
---|
0,0 → 1,563 |
/* |
* Copyright (c) 2008 Jiri Svoboda |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* |
* - Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* - 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. |
* - 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 ``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 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. |
*/ |
/** @addtogroup generic |
* @{ |
*/ |
/** |
* @file |
* @brief Udebug hooks and data structure management. |
* |
* Udebug is an interface that makes userspace debuggers possible. |
* |
* Functions in this file are executed directly in each thread, which |
* may or may not be the subject of debugging. The udebug_stoppable_begin/end() |
* functions are also executed in the clock interrupt handler. To avoid |
* deadlock, functions in this file are protected from the interrupt |
* by locking the recursive lock THREAD->udebug.int_lock (just an atomic |
* variable). This prevents udebug_stoppable_begin/end() from being |
* executed in the interrupt handler (they are skipped). |
* |
* Functions in udebug_ops.c and udebug_ipc.c execute in different threads, |
* so they needn't be protected from the (preemptible) interrupt-initiated |
* code. |
*/ |
#include <synch/waitq.h> |
#include <debug.h> |
#include <udebug/udebug.h> |
#include <errno.h> |
#include <arch.h> |
static inline void udebug_int_lock(void) |
{ |
atomic_inc(&THREAD->udebug.int_lock); |
} |
static inline void udebug_int_unlock(void) |
{ |
atomic_dec(&THREAD->udebug.int_lock); |
} |
/** Initialize udebug part of task structure. |
* |
* Called as part of task structure initialization. |
* @param ut Pointer to the structure to initialize. |
*/ |
void udebug_task_init(udebug_task_t *ut) |
{ |
mutex_initialize(&ut->lock, MUTEX_PASSIVE); |
ut->dt_state = UDEBUG_TS_INACTIVE; |
ut->begin_call = NULL; |
ut->not_stoppable_count = 0; |
ut->evmask = 0; |
} |
/** Initialize udebug part of thread structure. |
* |
* Called as part of thread structure initialization. |
* @param ut Pointer to the structure to initialize. |
*/ |
void udebug_thread_initialize(udebug_thread_t *ut) |
{ |
mutex_initialize(&ut->lock, MUTEX_PASSIVE); |
waitq_initialize(&ut->go_wq); |
/* |
* At the beginning the thread is stoppable, so int_lock be set, too. |
*/ |
atomic_set(&ut->int_lock, 1); |
ut->go_call = NULL; |
ut->stop = true; |
ut->stoppable = true; |
ut->debug_active = false; |
ut->cur_event = 0; /* none */ |
} |
/** Wait for a GO message. |
* |
* When a debugging event occurs in a thread or the thread is stopped, |
* this function is called to block the thread until a GO message |
* is received. |
* |
* @param wq The wait queue used by the thread to wait for GO messages. |
*/ |
static void udebug_wait_for_go(waitq_t *wq) |
{ |
int rc; |
ipl_t ipl; |
ipl = waitq_sleep_prepare(wq); |
wq->missed_wakeups = 0; /* Enforce blocking. */ |
rc = waitq_sleep_timeout_unsafe(wq, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE); |
waitq_sleep_finish(wq, rc, ipl); |
} |
/** Do a preliminary check that a debugging session is in progress. |
* |
* This only requires the THREAD->udebug.lock mutex (and not TASK->udebug.lock |
* mutex). For an undebugged task, this will never block (while there could be |
* collisions by different threads on the TASK mutex), thus improving SMP |
* perormance for undebugged tasks. |
* |
* @return True if the thread was in a debugging session when the function |
* checked, false otherwise. |
*/ |
static bool udebug_thread_precheck(void) |
{ |
bool res; |
mutex_lock(&THREAD->udebug.lock); |
res = THREAD->udebug.debug_active; |
mutex_unlock(&THREAD->udebug.lock); |
return res; |
} |
/** Start of stoppable section. |
* |
* A stoppable section is a section of code where if the thread can be stoped. In other words, |
* if a STOP operation is issued, the thread is guaranteed not to execute |
* any userspace instructions until the thread is resumed. |
* |
* Having stoppable sections is better than having stopping points, since |
* a thread can be stopped even when it is blocked indefinitely in a system |
* call (whereas it would not reach any stopping point). |
*/ |
void udebug_stoppable_begin(void) |
{ |
int nsc; |
call_t *db_call, *go_call; |
ASSERT(THREAD); |
ASSERT(TASK); |
udebug_int_lock(); |
/* Early check for undebugged tasks */ |
if (!udebug_thread_precheck()) { |
udebug_int_unlock(); |
return; |
} |
mutex_lock(&TASK->udebug.lock); |
nsc = --TASK->udebug.not_stoppable_count; |
/* Lock order OK, THREAD->udebug.lock is after TASK->udebug.lock */ |
mutex_lock(&THREAD->udebug.lock); |
ASSERT(THREAD->udebug.stoppable == false); |
THREAD->udebug.stoppable = true; |
if (TASK->udebug.dt_state == UDEBUG_TS_BEGINNING && nsc == 0) { |
/* |
* This was the last non-stoppable thread. Reply to |
* DEBUG_BEGIN call. |
*/ |
db_call = TASK->udebug.begin_call; |
ASSERT(db_call); |
TASK->udebug.dt_state = UDEBUG_TS_ACTIVE; |
TASK->udebug.begin_call = NULL; |
IPC_SET_RETVAL(db_call->data, 0); |
ipc_answer(&TASK->answerbox, db_call); |
} else if (TASK->udebug.dt_state == UDEBUG_TS_ACTIVE) { |
/* |
* Active debugging session |
*/ |
if (THREAD->udebug.debug_active && THREAD->udebug.stop) { |
/* |
* Thread was requested to stop - answer go call |
*/ |
/* Make sure nobody takes this call away from us */ |
go_call = THREAD->udebug.go_call; |
THREAD->udebug.go_call = NULL; |
ASSERT(go_call); |
IPC_SET_RETVAL(go_call->data, 0); |
IPC_SET_ARG1(go_call->data, UDEBUG_EVENT_STOP); |
THREAD->udebug.cur_event = UDEBUG_EVENT_STOP; |
ipc_answer(&TASK->answerbox, go_call); |
} |
} |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
} |
/** End of a stoppable section. |
* |
* This is the point where the thread will block if it is stopped. |
* (As, by definition, a stopped thread must not leave its stoppable section). |
*/ |
void udebug_stoppable_end(void) |
{ |
/* Early check for undebugged tasks */ |
if (!udebug_thread_precheck()) { |
udebug_int_unlock(); |
return; |
} |
restart: |
mutex_lock(&TASK->udebug.lock); |
mutex_lock(&THREAD->udebug.lock); |
if (THREAD->udebug.debug_active && |
THREAD->udebug.stop == true) { |
TASK->udebug.begin_call = NULL; |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
udebug_wait_for_go(&THREAD->udebug.go_wq); |
goto restart; |
/* must try again - have to lose stoppability atomically */ |
} else { |
++TASK->udebug.not_stoppable_count; |
ASSERT(THREAD->udebug.stoppable == true); |
THREAD->udebug.stoppable = false; |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
} |
udebug_int_unlock(); |
} |
/** Upon being scheduled to run, check if the current thread should stop. |
* |
* This function is called from clock(). Preemption is enabled. |
* interrupts are disabled, but since this is called after |
* being scheduled-in, we can enable them, if we're careful enough |
* not to allow arbitrary recursion or deadlock with the thread context. |
*/ |
void udebug_before_thread_runs(void) |
{ |
ipl_t ipl; |
return; |
ASSERT(!PREEMPTION_DISABLED); |
/* |
* Prevent agains re-entering, such as when preempted inside this |
* function. |
*/ |
if (atomic_get(&THREAD->udebug.int_lock) != 0) |
return; |
udebug_int_lock(); |
ipl = interrupts_enable(); |
/* Now we're free to do whatever we need (lock mutexes, sleep, etc.) */ |
/* Check if we're supposed to stop */ |
udebug_stoppable_begin(); |
udebug_stoppable_end(); |
interrupts_restore(ipl); |
udebug_int_unlock(); |
} |
/** Syscall event hook. |
* |
* Must be called before and after servicing a system call. This generates |
* a SYSCALL_B or SYSCALL_E event, depending on the value of @a end_variant. |
*/ |
void udebug_syscall_event(unative_t a1, unative_t a2, unative_t a3, |
unative_t a4, unative_t a5, unative_t a6, unative_t id, unative_t rc, |
bool end_variant) |
{ |
call_t *call; |
udebug_event_t etype; |
etype = end_variant ? UDEBUG_EVENT_SYSCALL_E : UDEBUG_EVENT_SYSCALL_B; |
udebug_int_lock(); |
/* Early check for undebugged tasks */ |
if (!udebug_thread_precheck()) { |
udebug_int_unlock(); |
return; |
} |
mutex_lock(&TASK->udebug.lock); |
mutex_lock(&THREAD->udebug.lock); |
/* Must only generate events when in debugging session and have go */ |
if (THREAD->udebug.debug_active != true || |
THREAD->udebug.stop == true || |
(TASK->udebug.evmask & UDEBUG_EVMASK(etype)) == 0) { |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
return; |
} |
//printf("udebug_syscall_event\n"); |
call = THREAD->udebug.go_call; |
THREAD->udebug.go_call = NULL; |
IPC_SET_RETVAL(call->data, 0); |
IPC_SET_ARG1(call->data, etype); |
IPC_SET_ARG2(call->data, id); |
IPC_SET_ARG3(call->data, rc); |
//printf("udebug_syscall_event/ipc_answer\n"); |
THREAD->udebug.syscall_args[0] = a1; |
THREAD->udebug.syscall_args[1] = a2; |
THREAD->udebug.syscall_args[2] = a3; |
THREAD->udebug.syscall_args[3] = a4; |
THREAD->udebug.syscall_args[4] = a5; |
THREAD->udebug.syscall_args[5] = a6; |
/* |
* Make sure udebug.stop is true when going to sleep |
* in case we get woken up by DEBUG_END. (At which |
* point it must be back to the initial true value). |
*/ |
THREAD->udebug.stop = true; |
THREAD->udebug.cur_event = etype; |
ipc_answer(&TASK->answerbox, call); |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
udebug_wait_for_go(&THREAD->udebug.go_wq); |
udebug_int_unlock(); |
} |
/** Thread-creation event hook. |
* |
* Must be called when a new userspace thread is created in the debugged |
* task. Generates a THREAD_B event. |
* |
* @param t Structure of the thread being created. Not locked, as the |
* thread is not executing yet. |
*/ |
void udebug_thread_b_event(struct thread *t) |
{ |
call_t *call; |
udebug_int_lock(); |
mutex_lock(&TASK->udebug.lock); |
mutex_lock(&THREAD->udebug.lock); |
LOG("udebug_thread_b_event\n"); |
LOG("- check state\n"); |
/* Must only generate events when in debugging session */ |
if (THREAD->udebug.debug_active != true) { |
LOG("- debug_active: %s, udebug.stop: %s\n", |
THREAD->udebug.debug_active ? "yes(+)" : "no(-)", |
THREAD->udebug.stop ? "yes(-)" : "no(+)"); |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
return; |
} |
LOG("- trigger event\n"); |
call = THREAD->udebug.go_call; |
THREAD->udebug.go_call = NULL; |
IPC_SET_RETVAL(call->data, 0); |
IPC_SET_ARG1(call->data, UDEBUG_EVENT_THREAD_B); |
IPC_SET_ARG2(call->data, (unative_t)t); |
/* |
* Make sure udebug.stop is true when going to sleep |
* in case we get woken up by DEBUG_END. (At which |
* point it must be back to the initial true value). |
*/ |
THREAD->udebug.stop = true; |
THREAD->udebug.cur_event = UDEBUG_EVENT_THREAD_B; |
ipc_answer(&TASK->answerbox, call); |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
LOG("- sleep\n"); |
udebug_wait_for_go(&THREAD->udebug.go_wq); |
udebug_int_unlock(); |
} |
/** Thread-termination event hook. |
* |
* Must be called when the current thread is terminating. |
* Generates a THREAD_E event. |
*/ |
void udebug_thread_e_event(void) |
{ |
call_t *call; |
udebug_int_lock(); |
mutex_lock(&TASK->udebug.lock); |
mutex_lock(&THREAD->udebug.lock); |
LOG("udebug_thread_e_event\n"); |
LOG("- check state\n"); |
/* Must only generate events when in debugging session */ |
if (THREAD->udebug.debug_active != true) { |
/* printf("- debug_active: %s, udebug.stop: %s\n", |
THREAD->udebug.debug_active ? "yes(+)" : "no(-)", |
THREAD->udebug.stop ? "yes(-)" : "no(+)");*/ |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
return; |
} |
LOG("- trigger event\n"); |
call = THREAD->udebug.go_call; |
THREAD->udebug.go_call = NULL; |
IPC_SET_RETVAL(call->data, 0); |
IPC_SET_ARG1(call->data, UDEBUG_EVENT_THREAD_E); |
/* Prevent any further debug activity in thread */ |
THREAD->udebug.debug_active = false; |
THREAD->udebug.cur_event = 0; /* none */ |
THREAD->udebug.stop = true; /* set to initial value */ |
ipc_answer(&TASK->answerbox, call); |
mutex_unlock(&THREAD->udebug.lock); |
mutex_unlock(&TASK->udebug.lock); |
/* Leave int_lock enabled */ |
/* This event does not sleep - debugging has finished in this thread */ |
} |
/** |
* Terminate task debugging session. |
* |
* Gracefully terminates the debugging session for a task. If the debugger |
* is still waiting for events on some threads, it will receive a |
* FINISHED event for each of them. |
* |
* @param ta Task structure. ta->udebug.lock must be already locked. |
* @return Zero on success or negative error code. |
*/ |
int udebug_task_cleanup(struct task *ta) |
{ |
thread_t *t; |
link_t *cur; |
int flags; |
ipl_t ipl; |
LOG("udebug_task_cleanup()\n"); |
LOG("task %" PRIu64 "\n", ta->taskid); |
udebug_int_lock(); |
if (ta->udebug.dt_state != UDEBUG_TS_BEGINNING && |
ta->udebug.dt_state != UDEBUG_TS_ACTIVE) { |
LOG("udebug_task_cleanup(): task not being debugged\n"); |
return EINVAL; |
} |
/* Finish debugging of all userspace threads */ |
for (cur = ta->th_head.next; cur != &ta->th_head; cur = cur->next) { |
t = list_get_instance(cur, thread_t, th_link); |
mutex_lock(&t->udebug.lock); |
ipl = interrupts_disable(); |
spinlock_lock(&t->lock); |
flags = t->flags; |
spinlock_unlock(&t->lock); |
interrupts_restore(ipl); |
/* Only process userspace threads */ |
if ((flags & THREAD_FLAG_USPACE) != 0) { |
/* Prevent any further debug activity in thread */ |
t->udebug.debug_active = false; |
t->udebug.cur_event = 0; /* none */ |
/* Still has go? */ |
if (t->udebug.stop == false) { |
/* |
* Yes, so clear go. As debug_active == false, |
* this doesn't affect anything. |
*/ |
t->udebug.stop = true; |
/* Answer GO call */ |
LOG("answer GO call with EVENT_FINISHED\n"); |
IPC_SET_RETVAL(t->udebug.go_call->data, 0); |
IPC_SET_ARG1(t->udebug.go_call->data, |
UDEBUG_EVENT_FINISHED); |
ipc_answer(&ta->answerbox, t->udebug.go_call); |
t->udebug.go_call = NULL; |
} else { |
/* |
* Debug_stop is already at initial value. |
* Yet this means the thread needs waking up. |
*/ |
/* |
* t's lock must not be held when calling |
* waitq_wakeup. |
*/ |
waitq_wakeup(&t->udebug.go_wq, WAKEUP_FIRST); |
} |
} |
mutex_unlock(&t->udebug.lock); |
} |
ta->udebug.dt_state = UDEBUG_TS_INACTIVE; |
ta->udebug.debugger = NULL; |
udebug_int_unlock(); |
return 0; |
} |
/** @} |
*/ |
/branches/sparc/kernel/generic/src/udebug/udebug_ops.c |
---|
0,0 → 1,523 |
/* |
* Copyright (c) 2008 Jiri Svoboda |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* |
* - Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* - 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. |
* - 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 ``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 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. |
*/ |
/** @addtogroup generic |
* @{ |
*/ |
/** |
* @file |
* @brief Udebug operations. |
* |
* Udebug operations on tasks and threads are implemented here. The |
* functions defined here are called from the udebug_ipc module |
* when servicing udebug IPC messages. |
*/ |
#include <debug.h> |
#include <proc/task.h> |
#include <proc/thread.h> |
#include <arch.h> |
#include <errno.h> |
#include <syscall/copy.h> |
#include <ipc/ipc.h> |
#include <udebug/udebug.h> |
#include <udebug/udebug_ops.h> |
/** |
* Prepare a thread for a debugging operation. |
* |
* Simply put, return thread t with t->udebug.lock held, |
* but only if it verifies all conditions. |
* |
* Specifically, verifies that thread t exists, is a userspace thread, |
* and belongs to the current task (TASK). Verifies, that the thread |
* has (or hasn't) go according to having_go (typically false). |
* It also locks t->udebug.lock, making sure that t->udebug.debug_active |
* is true - that the thread is in a valid debugging session. |
* |
* With this verified and the t->udebug.lock mutex held, it is ensured |
* that the thread cannot leave the debugging session, let alone cease |
* to exist. |
* |
* In this function, holding the TASK->udebug.lock mutex prevents the |
* thread from leaving the debugging session, while relaxing from |
* the t->lock spinlock to the t->udebug.lock mutex. |
* |
* @param t Pointer, need not at all be valid. |
* @param having_go Required thread state. |
* |
* Returns EOK if all went well, or an error code otherwise. |
*/ |
static int _thread_op_begin(thread_t *t, bool having_go) |
{ |
task_id_t taskid; |
ipl_t ipl; |
taskid = TASK->taskid; |
mutex_lock(&TASK->udebug.lock); |
/* thread_exists() must be called with threads_lock held */ |
ipl = interrupts_disable(); |
spinlock_lock(&threads_lock); |
if (!thread_exists(t)) { |
spinlock_unlock(&threads_lock); |
interrupts_restore(ipl); |
mutex_unlock(&TASK->udebug.lock); |
return ENOENT; |
} |
/* t->lock is enough to ensure the thread's existence */ |
spinlock_lock(&t->lock); |
spinlock_unlock(&threads_lock); |
/* Verify that 't' is a userspace thread */ |
if ((t->flags & THREAD_FLAG_USPACE) == 0) { |
/* It's not, deny its existence */ |
spinlock_unlock(&t->lock); |
interrupts_restore(ipl); |
mutex_unlock(&TASK->udebug.lock); |
return ENOENT; |
} |
/* Verify debugging state */ |
if (t->udebug.debug_active != true) { |
/* Not in debugging session or undesired GO state */ |
spinlock_unlock(&t->lock); |
interrupts_restore(ipl); |
mutex_unlock(&TASK->udebug.lock); |
return ENOENT; |
} |
/* |
* Since the thread has debug_active == true, TASK->udebug.lock |
* is enough to ensure its existence and that debug_active remains |
* true. |
*/ |
spinlock_unlock(&t->lock); |
interrupts_restore(ipl); |
/* Only mutex TASK->udebug.lock left */ |
/* Now verify that the thread belongs to the current task */ |
if (t->task != TASK) { |
/* No such thread belonging this task*/ |
mutex_unlock(&TASK->udebug.lock); |
return ENOENT; |
} |
/* |
* Now we need to grab the thread's debug lock for synchronization |
* of the threads stoppability/stop state. |
*/ |
mutex_lock(&t->udebug.lock); |
/* The big task mutex is no longer needed */ |
mutex_unlock(&TASK->udebug.lock); |
if (!t->udebug.stop != having_go) { |
/* Not in debugging session or undesired GO state */ |
mutex_unlock(&t->udebug.lock); |
return EINVAL; |
} |
/* Only t->udebug.lock left */ |
return EOK; /* All went well */ |
} |
/** End debugging operation on a thread. */ |
static void _thread_op_end(thread_t *t) |
{ |
mutex_unlock(&t->udebug.lock); |
} |
/** Begin debugging the current task. |
* |
* Initiates a debugging session for the current task (and its threads). |
* When the debugging session has started a reply will be sent to the |
* UDEBUG_BEGIN call. This may happen immediately in this function if |
* all the threads in this task are stoppable at the moment and in this |
* case the function returns 1. |
* |
* Otherwise the function returns 0 and the reply will be sent as soon as |
* all the threads become stoppable (i.e. they can be considered stopped). |
* |
* @param call The BEGIN call we are servicing. |
* @return 0 (OK, but not done yet), 1 (done) or negative error code. |
*/ |
int udebug_begin(call_t *call) |
{ |
int reply; |
thread_t *t; |
link_t *cur; |
LOG("udebug_begin()\n"); |
mutex_lock(&TASK->udebug.lock); |
LOG("debugging task %llu\n", TASK->taskid); |
if (TASK->udebug.dt_state != UDEBUG_TS_INACTIVE) { |
mutex_unlock(&TASK->udebug.lock); |
LOG("udebug_begin(): busy error\n"); |
return EBUSY; |
} |
TASK->udebug.dt_state = UDEBUG_TS_BEGINNING; |
TASK->udebug.begin_call = call; |
TASK->udebug.debugger = call->sender; |
if (TASK->udebug.not_stoppable_count == 0) { |
TASK->udebug.dt_state = UDEBUG_TS_ACTIVE; |
TASK->udebug.begin_call = NULL; |
reply = 1; /* immediate reply */ |
} else { |
reply = 0; /* no reply */ |
} |
/* Set udebug.debug_active on all of the task's userspace threads */ |
for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) { |
t = list_get_instance(cur, thread_t, th_link); |
mutex_lock(&t->udebug.lock); |
if ((t->flags & THREAD_FLAG_USPACE) != 0) |
t->udebug.debug_active = true; |
mutex_unlock(&t->udebug.lock); |
} |
mutex_unlock(&TASK->udebug.lock); |
LOG("udebug_begin() done (%s)\n", |
reply ? "reply" : "stoppability wait"); |
return reply; |
} |
/** Finish debugging the current task. |
* |
* Closes the debugging session for the current task. |
* @return Zero on success or negative error code. |
*/ |
int udebug_end(void) |
{ |
int rc; |
LOG("udebug_end()\n"); |
mutex_lock(&TASK->udebug.lock); |
LOG("task %" PRIu64 "\n", TASK->taskid); |
rc = udebug_task_cleanup(TASK); |
mutex_unlock(&TASK->udebug.lock); |
return rc; |
} |
/** Set the event mask. |
* |
* Sets the event mask that determines which events are enabled. |
* |
* @param mask Or combination of events that should be enabled. |
* @return Zero on success or negative error code. |
*/ |
int udebug_set_evmask(udebug_evmask_t mask) |
{ |
LOG("udebug_set_mask()\n"); |
mutex_lock(&TASK->udebug.lock); |
if (TASK->udebug.dt_state != UDEBUG_TS_ACTIVE) { |
mutex_unlock(&TASK->udebug.lock); |
LOG("udebug_set_mask(): not active debuging session\n"); |
return EINVAL; |
} |
TASK->udebug.evmask = mask; |
mutex_unlock(&TASK->udebug.lock); |
return 0; |
} |
/** Give thread GO. |
* |
* Upon recieving a go message, the thread is given GO. Having GO |
* means the thread is allowed to execute userspace code (until |
* a debugging event or STOP occurs, at which point the thread loses GO. |
* |
* @param t The thread to operate on (unlocked and need not be valid). |
* @param call The GO call that we are servicing. |
*/ |
int udebug_go(thread_t *t, call_t *call) |
{ |
int rc; |
/* On success, this will lock t->udebug.lock */ |
rc = _thread_op_begin(t, false); |
if (rc != EOK) { |
return rc; |
} |
t->udebug.go_call = call; |
t->udebug.stop = false; |
t->udebug.cur_event = 0; /* none */ |
/* |
* Neither t's lock nor threads_lock may be held during wakeup |
*/ |
waitq_wakeup(&t->udebug.go_wq, WAKEUP_FIRST); |
_thread_op_end(t); |
return 0; |
} |
/** Stop a thread (i.e. take its GO away) |
* |
* Generates a STOP event as soon as the thread becomes stoppable (i.e. |
* can be considered stopped). |
* |
* @param t The thread to operate on (unlocked and need not be valid). |
* @param call The GO call that we are servicing. |
*/ |
int udebug_stop(thread_t *t, call_t *call) |
{ |
int rc; |
LOG("udebug_stop()\n"); |
mutex_lock(&TASK->udebug.lock); |
/* |
* On success, this will lock t->udebug.lock. Note that this makes sure |
* the thread is not stopped. |
*/ |
rc = _thread_op_begin(t, true); |
if (rc != EOK) { |
return rc; |
} |
/* Take GO away from the thread */ |
t->udebug.stop = true; |
if (!t->udebug.stoppable) { |
/* Answer will be sent when the thread becomes stoppable */ |
_thread_op_end(t); |
return 0; |
} |
/* |
* Answer GO call |
*/ |
LOG("udebug_stop - answering go call\n"); |
/* Make sure nobody takes this call away from us */ |
call = t->udebug.go_call; |
t->udebug.go_call = NULL; |
IPC_SET_RETVAL(call->data, 0); |
IPC_SET_ARG1(call->data, UDEBUG_EVENT_STOP); |
LOG("udebug_stop/ipc_answer\n"); |
THREAD->udebug.cur_event = UDEBUG_EVENT_STOP; |
_thread_op_end(t); |
ipc_answer(&TASK->answerbox, call); |
mutex_unlock(&TASK->udebug.lock); |
LOG("udebog_stop/done\n"); |
return 0; |
} |
/** Read the list of userspace threads in the current task. |
* |
* The list takes the form of a sequence of thread hashes (i.e. the pointers |
* to thread structures). A buffer of size @a buf_size is allocated and |
* a pointer to it written to @a buffer. The sequence of hashes is written |
* into this buffer. |
* |
* If the sequence is longer than @a buf_size bytes, only as much hashes |
* as can fit are copied. The number of thread hashes copied is stored |
* in @a n. |
* |
* The rationale for having @a buf_size is that this function is only |
* used for servicing the THREAD_READ message, which always specifies |
* a maximum size for the userspace buffer. |
* |
* @param buffer The buffer for storing thread hashes. |
* @param buf_size Buffer size in bytes. |
* @param n The actual number of hashes copied will be stored here. |
*/ |
int udebug_thread_read(void **buffer, size_t buf_size, size_t *n) |
{ |
thread_t *t; |
link_t *cur; |
unative_t tid; |
unsigned copied_ids; |
ipl_t ipl; |
unative_t *id_buffer; |
int flags; |
size_t max_ids; |
LOG("udebug_thread_read()\n"); |
/* Allocate a buffer to hold thread IDs */ |
id_buffer = malloc(buf_size, 0); |
mutex_lock(&TASK->udebug.lock); |
/* Verify task state */ |
if (TASK->udebug.dt_state != UDEBUG_TS_ACTIVE) { |
mutex_unlock(&TASK->udebug.lock); |
return EINVAL; |
} |
ipl = interrupts_disable(); |
spinlock_lock(&TASK->lock); |
/* Copy down the thread IDs */ |
max_ids = buf_size / sizeof(unative_t); |
copied_ids = 0; |
/* FIXME: make sure the thread isn't past debug shutdown... */ |
for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) { |
/* Do not write past end of buffer */ |
if (copied_ids >= max_ids) break; |
t = list_get_instance(cur, thread_t, th_link); |
spinlock_lock(&t->lock); |
flags = t->flags; |
spinlock_unlock(&t->lock); |
/* Not interested in kernel threads */ |
if ((flags & THREAD_FLAG_USPACE) != 0) { |
/* Using thread struct pointer as identification hash */ |
tid = (unative_t) t; |
id_buffer[copied_ids++] = tid; |
} |
} |
spinlock_unlock(&TASK->lock); |
interrupts_restore(ipl); |
mutex_unlock(&TASK->udebug.lock); |
*buffer = id_buffer; |
*n = copied_ids * sizeof(unative_t); |
return 0; |
} |
/** Read the arguments of a system call. |
* |
* The arguments of the system call being being executed are copied |
* to an allocated buffer and a pointer to it is written to @a buffer. |
* The size of the buffer is exactly such that it can hold the maximum number |
* of system-call arguments. |
* |
* Unless the thread is currently blocked in a SYSCALL_B or SYSCALL_E event, |
* this function will fail with an EINVAL error code. |
* |
* @param buffer The buffer for storing thread hashes. |
*/ |
int udebug_args_read(thread_t *t, void **buffer) |
{ |
int rc; |
unative_t *arg_buffer; |
/* Prepare a buffer to hold the arguments */ |
arg_buffer = malloc(6 * sizeof(unative_t), 0); |
/* On success, this will lock t->udebug.lock */ |
rc = _thread_op_begin(t, false); |
if (rc != EOK) { |
return rc; |
} |
/* Additionally we need to verify that we are inside a syscall */ |
if (t->udebug.cur_event != UDEBUG_EVENT_SYSCALL_B && |
t->udebug.cur_event != UDEBUG_EVENT_SYSCALL_E) { |
_thread_op_end(t); |
return EINVAL; |
} |
/* Copy to a local buffer before releasing the lock */ |
memcpy(arg_buffer, t->udebug.syscall_args, 6 * sizeof(unative_t)); |
_thread_op_end(t); |
*buffer = arg_buffer; |
return 0; |
} |
/** Read the memory of the debugged task. |
* |
* Reads @a n bytes from the address space of the debugged task, starting |
* from @a uspace_addr. The bytes are copied into an allocated buffer |
* and a pointer to it is written into @a buffer. |
* |
* @param uspace_addr Address from where to start reading. |
* @param n Number of bytes to read. |
* @param buffer For storing a pointer to the allocated buffer. |
*/ |
int udebug_mem_read(unative_t uspace_addr, size_t n, void **buffer) |
{ |
void *data_buffer; |
int rc; |
/* Verify task state */ |
mutex_lock(&TASK->udebug.lock); |
if (TASK->udebug.dt_state != UDEBUG_TS_ACTIVE) { |
mutex_unlock(&TASK->udebug.lock); |
return EBUSY; |
} |
data_buffer = malloc(n, 0); |
/* NOTE: this is not strictly from a syscall... but that shouldn't |
* be a problem */ |
rc = copy_from_uspace(data_buffer, (void *)uspace_addr, n); |
mutex_unlock(&TASK->udebug.lock); |
if (rc != 0) return rc; |
*buffer = data_buffer; |
return 0; |
} |
/** @} |
*/ |