iBoot/apps/EmbeddedIOP/main.c

619 lines
19 KiB
C

/*
* Copyright (C) 2007-2014 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include <arch.h>
#include <debug.h>
#include <cbuffer.h>
#include <platform.h>
#include <platform/memmap.h>
#include <platform/int.h>
#include <platform/soc/hwisr.h>
#include <platform/timer.h>
#include <sys.h>
#include <sys/boot.h>
#include <sys/menu.h>
#include <sys/task.h>
#include <lib/mib_def.h>
#include "iop.h"
#include "qwi.h"
#include "qwi_protocol.h"
#include "EmbeddedIOPProtocol.h"
#include "clock_management.h"
#include "clock_stepping.h"
/* XXX should get these automagically */
#if WITH_FUNCTION_SDIO
extern int iop_sdio_task(void *arg);
#endif
/* configuration, patched by host before we start */
struct iop_configuration _iop_config = {
.magic = IOP_CONFIG_MAGIC
};
static int host_channel;
static struct task_event host_command_event;
static int iop_message_channel;
static struct task_event iop_message_event;
static struct task_event iop_console_event;
static union iop_message *iop_message_buffer;
static bool iop_suspended;
struct iop_ping_tracker gControlMessages[128];
int gControlMessageCount = 0;
static int no_idle_task(void *arg __unused);
static int host_command_task(void *cfg);
static void host_command_hook(void *user_data);
static bool host_command_process(void);
static union iop_message *iop_message_alloc(utime_t allowed_delay);
static void iop_message_wakeup(void *arg __unused);
static void iop_message_tty(CBUFFER *pcb);
static int host_console_task(void *arg __unused);
/* the host command task is just another function */
IOP_FUNCTION(iop, host_command_task, 1024, IOP_CONTROL_CHANNEL);
/* sleep/wakeup hook */
static void sleep_hook(int mode);
IOP_SLEEP_HOOK(iop, sleep_hook);
/* console */
CBUFFER console_buffer;
#ifndef APPLICATION_CONSOLE_BUFFER
# define APPLICATION_CONSOLE_BUFFER 256
#endif
/* NMI */
static void iop_nmi_handler(void *junk);
#if DEBUG_BUILD
static char logo[] = {
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x2f, 0x5c, 0x2c, 0x25, 0x2c, 0x5f, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5c,
0x25, 0x25, 0x25, 0x2f, 0x2c, 0x5c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x2e, 0x2d, 0x22, 0x25,
0x25, 0x7c, 0x2f, 0x2f, 0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x2e, 0x27, 0x20, 0x20, 0x2e, 0x2d, 0x22, 0x20,
0x20, 0x2f, 0x25, 0x25, 0x25, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x46, 0x4f, 0x52, 0x20, 0x50, 0x4f, 0x4e, 0x59, 0x21, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x2e, 0x2d, 0x27, 0x5f, 0x2e, 0x2d,
0x22, 0x20, 0x30, 0x29, 0x20, 0x20, 0x20, 0x5c, 0x25, 0x25, 0x25, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2e, 0x5c, 0x2e, 0x27, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5c, 0x25, 0x25,
0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5c, 0x20, 0x2f, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x5f, 0x2c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x25, 0x25, 0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x22,
0x2d, 0x2d, 0x2d, 0x22, 0x7e, 0x60, 0x5c, 0x20, 0x20, 0x20, 0x5f, 0x2c,
0x2a, 0x27, 0x5c, 0x25, 0x25, 0x27, 0x20, 0x20, 0x20, 0x5f, 0x2c, 0x2d,
0x2d, 0x22, 0x22, 0x22, 0x22, 0x2d, 0x2c, 0x25, 0x25, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x29, 0x2a, 0x5e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x22,
0x22, 0x7e, 0x7e, 0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x5c, 0x25, 0x25, 0x25, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x2f, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x5c, 0x25, 0x25, 0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x5f, 0x2e, 0x2d, 0x60, 0x2f, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7c, 0x25,
0x25, 0x2c, 0x5f, 0x5f, 0x5f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f,
0x2e, 0x2d, 0x22, 0x20, 0x20, 0x20, 0x2f, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x2c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x2c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2c, 0x7c,
0x25, 0x25, 0x20, 0x20, 0x20, 0x2e, 0x60, 0x5c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2f, 0x5c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x5c, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x5c, 0x25, 0x27, 0x20, 0x20, 0x20, 0x5c, 0x20, 0x2f, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x5c, 0x20, 0x5c, 0x20, 0x5f, 0x2c, 0x2f, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x60, 0x7e, 0x2d, 0x2e, 0x5f, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x2c, 0x60, 0x5c,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5c, 0x60, 0x22, 0x22, 0x7e, 0x7e,
0x60, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x22, 0x60, 0x20, 0x2f,
0x2d, 0x2e, 0x2c, 0x5f, 0x20, 0x2f, 0x27, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x60, 0x7e, 0x22, 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x7e, 0x20, 0x20,
0x20, 0x20, 0x60, 0x5c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5c, 0x0a, 0x20,
0x20, 0x20, 0x6a, 0x67, 0x73, 0x20, 0x20, 0x20, 0x5c, 0x5f, 0x5f, 0x5f,
0x2c, 0x27, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x5c, 0x2e, 0x2d, 0x22, 0x60, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60,
0x2d, 0x2d, 0x27, 0x0a, 0x00
};
#endif
int
_main(void)
{
void **func_cursor;
const struct iop_function *func;
#if DEBUG_BUILD
printf("\n%s", logo);
#endif
dprintf(DEBUG_INFO, "####\n#### " CONFIG_PROGNAME_STRING ": " XBS_BUILD_TAG "\n####\n");
/* initialize the cpu */
dprintf(DEBUG_INFO, "doing CPU init\n");
arch_cpu_init(false);
/* do early initialization of hardware */
dprintf(DEBUG_INFO, "doing early platform hardware init\n");
platform_early_init();
/* bring up system services (cpu, tasks, callout) */
dprintf(DEBUG_INFO, "doing system init\n");
sys_init();
#ifdef HEAP_EXT_SIZE
/*
* If we have extra memory for the heap, add it. This needs
* to happen after main memory has been initialized.
*/
dprintf(DEBUG_INFO, "Adding %llu bytes at %p to heap.\n", HEAP_EXT_SIZE, (void *)HEAP_EXT_BASE);
heap_add_chunk((void *)HEAP_EXT_BASE, HEAP_EXT_SIZE, true);
#endif
/* register our doorbell handler and enable the doorbell */
platform_init_iop_doorbell((int_handler)qwi_doorbell, NULL);
/*
* Start the host console task and give it a chance to run, so that we have
* a console past this point.
*/
task_start(task_create("console", host_console_task, NULL, 512));
task_yield();
/*
* Iterate IOP function tasks, starting each as we go.
*/
LINKER_SET_FOREACH(func_cursor, iop_function) {
func = (const struct iop_function *)*func_cursor;
if (NULL != func->entrypoint) {
dprintf(DEBUG_INFO, "starting %s\n", func->function_name);
task_start(task_create(
func->function_name,
func->entrypoint,
(void *)&_iop_config.channel[func->control_channel],
func->stack_allocation));
}
}
#if DEBUG_BUILD
dprintf(DEBUG_INFO, "starting debug console\n");
task_start(task_create("menu", menu_task, NULL, 8192));
#endif
/* start the slopsucker task if we don't want to go idle */
if (_iop_config.options & IOP_OPTION_NO_IDLE)
task_start(task_create("slopsucker", no_idle_task, NULL, 512));
/* start the slopsucker task if we don't want to go idle */
if (_iop_config.options & IOP_OPTION_DO_CLOCK_MGMNT)
SetParticipateInClockStateManagement(true);
/* if the task manager needs retuning, do so */
if (_iop_config.deep_idle_us != 0)
task_set_idle_threshold(_iop_config.deep_idle_us);
/* register our NMI handler */
platform_init_nmi(iop_nmi_handler, NULL);
dprintf(DEBUG_INFO, "bootstrap task terminating\n");
task_exit(0);
}
/*
* This task is run when we don't want the IOP to idle, normally when
* JTAG debugging is enabled on the host.
*/
static int
no_idle_task(void *arg __unused)
{
for (;;) {
task_yield();
}
return(0);
}
static int
host_command_task(void *cfg)
{
struct iop_channel_config *channel = (struct iop_channel_config *)cfg;
dprintf(DEBUG_INFO, "## IOP control task starting\n");
/* establish the host communications channel */
event_init(&host_command_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
dprintf(DEBUG_INFO, "## opening host channel\n");
host_channel = qwi_instantiate_channel(
"iop control",
QWI_ROLE_CONSUMER,
channel->ring_size,
(void *)mem_static_map_cached(channel->ring_base),
host_command_hook,
&host_command_event);
iop_suspended = false;
for (;;) {
/*
* Spin handling host commands
*/
while (host_command_process())
;
if (!iop_suspended) {
/*
* Normal operation; wait for an event signalling new work.
*/
dprintf(DEBUG_SPEW, "## waiting for host command\n");
event_wait(&host_command_event);
} else {
/*
* We are suspended; act as though we had received a doorbell
* interrupt.
*/
host_command_hook(&host_command_event);
}
}
return(0);
}
static void
host_command_hook(void *user_data)
{
struct task_event *event = (struct task_event *)user_data;
/* pass it through to task context */
event_signal(event);
}
static bool
host_command_process(void)
{
uint32_t message;
union iop_command *command;
bool do_sleep = false;
void **hook_cursor;
const struct iop_sleep_hook *hook;
struct idle_statistics stats; // XXX this is 'big', almost never used; ok on stack?
/* look to see if there's an item waiting for us */
if (qwi_receive_item(host_channel, &message) == -1)
return(false);
/* find the command structure based on the message */
command = (union iop_command *)mem_static_map_cached(message);
if (command == MAP_FAILED)
panic("received bad command pointer on control channel");
/*
* Flush any cached item contents we might have lying around - we are guaranteed
* that the command size is a multiple of our cacheline size.
*/
platform_cache_operation(CACHE_INVALIDATE, (void *)command, sizeof(*command));
switch (command->generic.opcode) {
case IOP_CMD_NOP:
// dprintf(DEBUG_INFO, "## host NOP\n");
// Store information on ping commands received, in a global structure
// For use during debug of watchdog time outs
command->generic.result = IOP_RESULT_SUCCESS;
gControlMessages[gControlMessageCount].timestamp = timer_get_ticks();
gControlMessages[gControlMessageCount].record.opcode = command->ping.opcode;
gControlMessages[gControlMessageCount].record.result = command->ping.result;
gControlMessages[gControlMessageCount].record.ping_id = command->ping.ping_id;
gControlMessageCount = (gControlMessageCount + 1) % 128;
break;
case IOP_CMD_TTYIN:
debug_pushchar(command->ttyin.c);
command->generic.result = IOP_RESULT_SUCCESS;
break;
case IOP_CMD_SLEEP:
dprintf(DEBUG_INFO, "## host SLEEP\n");
do_sleep = true;
command->generic.result = IOP_RESULT_SUCCESS;
break;
case IOP_CMD_SUSPEND:
dprintf(DEBUG_INFO, "## host SUSPEND\n");
iop_suspended = true;
platform_cache_operation(CACHE_CLEAN, 0, 0);
command->generic.result = IOP_RESULT_SUCCESS;
break;
case IOP_CMD_RESUME:
dprintf(DEBUG_INFO, "## host RESUME\n");
iop_suspended = false;
command->generic.result = IOP_RESULT_SUCCESS;
break;
case IOP_CMD_INSTRUMENT:
dprintf(DEBUG_INFO, "## host INSTRUMENT\n");
iop_suspended = false;
task_get_statistics(& stats);
command->generic.result = IOP_RESULT_SUCCESS;
command->instr.uptime_ticks = stats.uptime_ticks;
command->instr.idles = stats.idles;
command->instr.deep_idles = stats.deep_idles;
command->instr.deep_idle_ticks = stats.deep_idle_ticks;
command->instr.idle_ticks = stats.idle_ticks;
command->instr.threshold_us = stats.threshold_us;
command->instr.ticksHz = stats.ticksHz;
break;
default:
dprintf(DEBUG_CRITICAL, "## unrecognised host opcode 0x%x in command %p\n", command->generic.opcode, command);
command->generic.result = IOP_RESULT_ERROR;
break;
}
/* this should never fail because we just pulled an item out and so we must own a slot */
platform_cache_operation(CACHE_CLEAN, (void *)command, sizeof(*command));
qwi_send_item(host_channel, message);
/* sleep *after* we have replied */
if (do_sleep) {
/* tell anyone that cares that we're sleeping */
LINKER_SET_FOREACH(hook_cursor, iop_sleep_hook) {
hook = (const struct iop_sleep_hook *)*hook_cursor;
hook->func(IOP_SLEEP_MODE_SLEEPING);
}
/* go to sleep */
platform_sleep();
/* and tell them we've woken up again */
LINKER_SET_FOREACH(hook_cursor, iop_sleep_hook) {
hook = (const struct iop_sleep_hook *)*hook_cursor;
hook->func(IOP_SLEEP_MODE_WAKING);
}
}
return(true);
}
static void
sleep_hook(int mode)
{
switch(mode) {
case IOP_SLEEP_MODE_WAKING:
dprintf(DEBUG_INFO, "enabling doorbell\n");
platform_unmask_doorbell();
// On some system (like AE2), put the system into lowest power state
// SetClockState will be no-op on other systems
SetClockState(kClockRequestPowerManager, kClockValueLow);
break;
case IOP_SLEEP_MODE_SLEEPING:
dprintf(DEBUG_INFO, "disabling doorbell\n");
// on some system(like AE2), restore clocks to high before we go to sleep
SetClockState(kClockRequestPowerManager, kClockValueHigh);
platform_mask_doorbell();
break;
}
}
/*
* Putchar hook.
*/
char gIOPPanicLog[IOP_PANIC_LOG_SIZE];
u_int32_t gIOPPanicBytes;
void
application_putchar(int c)
{
/* if we are panicking, append the data to the panic log */
if (NULL != gPanicStr) {
/*
* Printf emits nuls because it's not smart enough to tell the difference between
* strings and streams, so strip them here.
*/
if ((0 != c) && (gIOPPanicBytes < IOP_PANIC_LOG_SIZE))
gIOPPanicLog[gIOPPanicBytes++] = c;
} else {
/*
* If we have a console buffer, and this isn't a nul, try to stuff it
* there and wake up the output task.
*/
if ((true == cb_initialized(&console_buffer)) && (0 != c)) {
#if APPLICATION_CONSOLE_RELIABLE
/* Try to be lossless - yield if the buffer is full */
while (!cb_free_space(&console_buffer)) {
task_yield();
}
#endif
(void)cb_putc(&console_buffer, c);
event_signal(&iop_console_event);
}
}
}
/*
* NMI handling
*/
static void
iop_nmi_handler(void *junk)
{
panic("NMI");
}
/*******************************************************************************
* IOP-to-host messaging support
*
* N.B. Since this is used to carry console output, (d)printf should not be
* called in this code.
*/
/*
* Allocate a message to send to the host.
*
* Note that it is not safe to block between allocating a message and sending it.
*/
static union iop_message *
iop_message_alloc(utime_t allowed_delay)
{
utime_t deadline, t;
int idx;
/* nothing we can do if we don't have a buffer */
if (NULL == iop_message_buffer)
return(NULL);
/* try to get a slot right away */
if (-1 != (idx = qwi_next_send_index(iop_message_channel)))
return(iop_message_buffer + idx);
if (IOP_MESSAGE_NO_WAIT == allowed_delay)
return(NULL);
/* spin waiting for a free slot */
deadline = system_time() + allowed_delay;
for (;;) {
/* get the current time and see if we've timed out */
t = system_time();
if (t >= deadline)
return(NULL);
/* sleep waiting for notification or timeout */
event_wait_timeout(&iop_message_event, deadline - t);
/* try again to get a message slot */
if (-1 != (idx = qwi_next_send_index(iop_message_channel)))
return(iop_message_buffer + idx);
}
}
/*
* Reclaim a message the host has accepted.
*/
static void
iop_message_wakeup(void *arg __unused)
{
bool replies;
uint32_t message;
/* reap message replies */
replies = false;
while (qwi_receive_item(iop_message_channel, &message) != -1)
replies = true;
/* if we got at least one reply, wake anyone trying to send */
if (replies)
event_signal(&iop_message_event);
}
/*
* Try to send a trace message to the host.
*/
void
iop_message_trace(const char *ident, uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
union iop_message *msg;
uint64_t timestamp;
/* get the raw time shared with the host */
timestamp = timer_get_ticks();
/* now get a message - wait a little while for it but not forever as we might deadlock the host */
if (NULL != (msg = iop_message_alloc(1000))) {
/* populate the message */
msg->gen.opcode = IOP_MSG_TRACE;
msg->gen.size = sizeof(msg->trace);
msg->trace.ident = mem_static_map_physical((uint32_t)ident);
msg->trace.arg[0] = arg0;
msg->trace.arg[1] = arg1;
msg->trace.arg[2] = arg2;
msg->trace.arg[3] = arg3;
msg->trace.timestamp = timestamp;
/* push it to memory */
platform_cache_operation(CACHE_CLEAN, msg, IOP_MESSAGE_MAX);
/* and give it to the host */
qwi_send_item(iop_message_channel, QWI_ENCODE_ORDINAL(msg - iop_message_buffer));
}
}
/*
* Try to send a tty message to the host.
*/
static void
iop_message_tty(CBUFFER *pcb)
{
union iop_message *msg;
unsigned int count;
/* get a message */
if (NULL == (msg = iop_message_alloc(IOP_MESSAGE_WAIT_FOREVER)))
panic("the console is dead, Jim");
/* get as many bytes from the cbuffer into our message as we can */
count = cb_read(pcb, (unsigned char *)msg->tty.bytes, IOP_MSG_TTY_MAXLEN);
msg->gen.opcode = IOP_MSG_TTY;
msg->gen.size = sizeof(msg->gen) + count;
/* push it to memory */
platform_cache_operation(CACHE_CLEAN, msg, IOP_MESSAGE_MAX);
/* and give it to the host */
qwi_send_item(iop_message_channel, QWI_ENCODE_ORDINAL(msg - iop_message_buffer));
}
/*
* Buffer task outputting to the console.
*/
static int
host_console_task(void *arg __unused)
{
/* allocate a cbuffer for host console output */
cb_create(&console_buffer, APPLICATION_CONSOLE_BUFFER);
/* establish the message channel */
iop_message_buffer = (union iop_message *)mem_static_map_cached(_iop_config.message_buffer);
event_init(&iop_message_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
event_init(&iop_console_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
dprintf(DEBUG_INFO, "## opening IOP message channel\n");
iop_message_channel = qwi_instantiate_channel(
"iop message",
QWI_ROLE_PRODUCER,
_iop_config.channel[IOP_MESSAGE_CHANNEL].ring_size,
(void *)mem_static_map_cached(_iop_config.channel[IOP_MESSAGE_CHANNEL].ring_base),
iop_message_wakeup,
NULL);
for (;;) {
/* drain the console buffer */
while (cb_readable_size(&console_buffer) > 0)
iop_message_tty(&console_buffer);
/* wait for more */
event_wait(&iop_console_event);
}
return(0);
}