mirror of
https://github.com/rvtr/ctr_firmware.git
synced 2025-10-31 07:51:08 -04:00
チックのタイマーチャンネル変更対応。 git-svn-id: file:///Volumes/Transfer/gigaleak_20231201/2020-09-30%20-%20paladin.7z/paladin/ctr_firmware@123 b871894f-2f95-9b40-918c-086798483c85
703 lines
18 KiB
C
703 lines
18 KiB
C
/*---------------------------------------------------------------------------*
|
||
Project: CtrBrom - libraries - OS
|
||
File: os_alarm.c
|
||
|
||
Copyright 2008 Nintendo. All rights reserved.
|
||
|
||
These coded instructions, statements, and computer programs contain
|
||
proprietary information of Nintendo of America Inc. and/or Nintendo
|
||
Company Ltd., and are protected by Federal copyright law. They may
|
||
not be disclosed to third parties or copied or duplicated in any form,
|
||
in whole or in part, without the prior written consent of Nintendo.
|
||
|
||
$Date:: $
|
||
$Rev$
|
||
$Author$
|
||
*---------------------------------------------------------------------------*/
|
||
#include <brom/os.h>
|
||
|
||
#define osPanic(...) ((void)0)
|
||
|
||
//----------------------------------------------------------------------
|
||
//---- timer number alarm uses
|
||
#define OSi_ALARM_TIMER OS_TIMER_1
|
||
|
||
#ifdef SDK_ARM11
|
||
//---- timer interrupt ID
|
||
#define OSi_ALARM_IE_TIMER_ID OS_INTR_ID_WATCHDOG
|
||
|
||
#else // SDK_ARM9
|
||
//---- timer interrupt ID
|
||
#define OSi_ALARM_IE_TIMER_ID OS_INTR_ID_TIMER1
|
||
|
||
//---- timer interrupt mask (must be same number with OSi_ALARM_TIMER)
|
||
#define OSi_ALARM_IE_TIMER OS_IE_TIMER1
|
||
|
||
//---- timer control setting for alarm
|
||
#define OSi_ALARM_TIMERCONTROL ( REG_OS_TM0CNT_H_E_MASK | REG_OS_TM0CNT_H_I_MASK | OS_TIMER_PRESCALER_64 )
|
||
|
||
#endif // SDK_ARM9
|
||
|
||
//---- flag for initialization alarm
|
||
static u16 i_osUseAlarm = FALSE;
|
||
|
||
//---- alarm queue
|
||
static struct OSiAlarmQueue i_osAlarmQueue;
|
||
|
||
|
||
u16 i_osIsTimerReserved(int timerNum);
|
||
void i_osSetTimerReserved(int timerNum);
|
||
void i_osUnsetTimerReserved(int timerNum);
|
||
|
||
static void i_osSetTimer(OSAlarm *alarm);
|
||
static void i_osInsertAlarm(OSAlarm *alarm, OSTick fire);
|
||
|
||
static void i_osAlarmHandler(void *arg);
|
||
void i_osArrangeTimer(void);
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: i_osSetTimer
|
||
|
||
Description: set Timer
|
||
|
||
Arguments: alarm pointer to alarm to set timer
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
static void i_osSetTimer(OSAlarm *alarm)
|
||
{
|
||
OSTimerCount timerCount;
|
||
s64 delta;
|
||
|
||
OSTick tick = osGetTick();
|
||
|
||
delta = (s64)(alarm->fire - tick);
|
||
|
||
//---- let timer be disable
|
||
osSetTimerControl(OSi_ALARM_TIMER, 0);
|
||
|
||
//---- set interrupt callback
|
||
osSetInterruptHandler( OSi_ALARM_IE_TIMER_ID, i_osArrangeTimer );
|
||
// osEnterTimerCallback(OSi_ALARM_TIMER, i_osAlarmHandler, NULL);
|
||
|
||
//---- set count and let timer be enable
|
||
if (delta < 0)
|
||
{
|
||
#ifdef SDK_ARM11
|
||
timerCount = 1;
|
||
#else // SDK_ARM9
|
||
// ARM9<4D><39>0xFFFF<46>ݒ<EFBFBD><DD92>֎~
|
||
timerCount = (u16)~1;
|
||
#endif // SDK_ARM9
|
||
}
|
||
else if (delta < OS_TICK_LO_LIMIT)
|
||
{
|
||
#ifdef SDK_ARM11
|
||
timerCount = delta;
|
||
#else // SDK_ARM9
|
||
timerCount = (u16)(~delta);
|
||
#endif // SDK_ARM9
|
||
}
|
||
else
|
||
{
|
||
#ifdef SDK_ARM11
|
||
timerCount = OS_TICK_LO_LIMIT;
|
||
#else // SDK_ARM9
|
||
timerCount = 0;
|
||
#endif // SDK_ARM9
|
||
}
|
||
|
||
//osPrintf( "**i_osSetTimer alarm=%x, fire=%llx time=%llx delta=%lld timeCount=%x \n", alarm, alarm->fire, time, delta, (int)timerCount );
|
||
|
||
#ifdef SDK_ARM11
|
||
osStartTimer(OSi_ALARM_TIMER, timerCount, 0);
|
||
#else // SDK_ARM9
|
||
osSetTimerCount((OSTimer)OSi_ALARM_TIMER, timerCount);
|
||
osSetTimerControl(OSi_ALARM_TIMER, (u16)OSi_ALARM_TIMERCONTROL);
|
||
#endif // SDK_ARM9
|
||
|
||
//---- TIMER IRQ Enable
|
||
(void)osEnableInterruptID(OSi_ALARM_IE_TIMER_ID);
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osInitAlarm
|
||
|
||
Description: Initialize alarm system
|
||
|
||
Arguments: None.
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osInitAlarm(void)
|
||
{
|
||
if (!i_osUseAlarm)
|
||
{
|
||
i_osUseAlarm = TRUE;
|
||
|
||
//---- check if tick system is available
|
||
SDK_ASSERTMSG(osIsTickAvailable(), "osInitAlarm: alarm system needs of tick system.");
|
||
|
||
//---- OS reserves OSi_ALARM_TIMER
|
||
SDK_ASSERT(!i_osIsTimerReserved(OSi_ALARM_TIMER));
|
||
i_osSetTimerReserved(OSi_ALARM_TIMER);
|
||
#ifdef SDK_ARM11
|
||
osDisableTimerReload(OSi_ALARM_TIMER);
|
||
#endif // SDK_ARM11
|
||
|
||
//---- clear alarm list
|
||
i_osAlarmQueue.head = NULL;
|
||
i_osAlarmQueue.tail = NULL;
|
||
|
||
//---- TIMER IRQ Disable
|
||
(void)osDisableInterruptID(OSi_ALARM_IE_TIMER_ID);
|
||
}
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osEndAlarm
|
||
|
||
Description: end alarm system
|
||
|
||
Arguments: None
|
||
|
||
Returns: None
|
||
*---------------------------------------------------------------------------*/
|
||
void osEndAlarm(void)
|
||
{
|
||
OSIntrMode enabled;
|
||
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
enabled = osDisableInterrupts();
|
||
|
||
//---- check if any alarm exists
|
||
if (i_osUseAlarm)
|
||
{
|
||
SDK_ASSERTMSG(!i_osAlarmQueue.head,
|
||
"osEndAlarm: Cannot end alarm system while using alarm.");
|
||
|
||
#ifdef SDK_ARM9
|
||
//---- unset timer reservation by OS
|
||
SDK_ASSERT(i_osIsTimerReserved(OSi_ALARM_TIMER));
|
||
i_osUnsetTimerReserved(OSi_ALARM_TIMER);
|
||
#endif // SDK_ARM9
|
||
|
||
i_osUseAlarm = FALSE;
|
||
}
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osIsAlarmAvailable
|
||
|
||
Description: check alarm system is available
|
||
|
||
Arguments: None
|
||
|
||
Returns: if available, TRUE.
|
||
*---------------------------------------------------------------------------*/
|
||
BOOL osIsAlarmAvailable(void)
|
||
{
|
||
return i_osUseAlarm;
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osCreateAlarm
|
||
|
||
Description: Create alarm
|
||
|
||
Arguments: alarm pointer to alarm to be initialized
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osCreateAlarm(OSAlarm *alarm)
|
||
{
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
SDK_ASSERT(alarm);
|
||
|
||
alarm->handler = 0;
|
||
alarm->tag = 0;
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: i_osInsertAlarm
|
||
|
||
Description: Insert alarm. Needs to be called interrupts disabled.
|
||
|
||
Arguments: alarm pointer to alarm to be set
|
||
fire tick to fire (only for one shot alarm)
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
static void i_osInsertAlarm(OSAlarm *alarm, OSTick fire)
|
||
{
|
||
OSAlarm *prev;
|
||
OSAlarm *next;
|
||
|
||
//---- caluculate next fire for periodic alarm
|
||
if (alarm->period > 0)
|
||
{
|
||
OSTick tick = osGetTick();
|
||
|
||
fire = alarm->start;
|
||
if (alarm->start < tick)
|
||
{
|
||
fire += alarm->period * ((tick - alarm->start) / alarm->period + 1);
|
||
}
|
||
}
|
||
|
||
//---- set tick to fire
|
||
alarm->fire = fire;
|
||
|
||
//---- insert to list
|
||
for (next = i_osAlarmQueue.head; next; next = next->next)
|
||
{
|
||
// if ( next->fire <= fire )
|
||
if ((s64)(fire - next->fire) >= 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
//---- insert alarm before 'next'
|
||
alarm->prev = next->prev;
|
||
next->prev = alarm;
|
||
alarm->next = next;
|
||
prev = alarm->prev;
|
||
if (prev)
|
||
{
|
||
prev->next = alarm;
|
||
}
|
||
else
|
||
{
|
||
i_osAlarmQueue.head = alarm;
|
||
i_osSetTimer(alarm);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
//---- insert alarm after tail
|
||
alarm->next = 0;
|
||
prev = i_osAlarmQueue.tail;
|
||
i_osAlarmQueue.tail = alarm;
|
||
alarm->prev = prev;
|
||
if (prev)
|
||
{
|
||
prev->next = alarm;
|
||
}
|
||
else
|
||
{
|
||
i_osAlarmQueue.head = i_osAlarmQueue.tail = alarm;
|
||
i_osSetTimer(alarm);
|
||
}
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osSetAlarm
|
||
|
||
Description: Set alarm as a relative tick
|
||
|
||
Arguments: alarm pointer to alarm to be set
|
||
tick ticks to count before firing
|
||
handler alarm handler to be called
|
||
arg argument of handler
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osSetAlarm(OSAlarm *alarm, OSTick tick, OSAlarmHandler handler, void *arg)
|
||
{
|
||
OSIntrMode enabled;
|
||
|
||
//osPrintf( "**osSetAlarm e=%x alarm=%x tick=%x handler%x\n", enabled, alarm, tick, handler );
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
SDK_ASSERTMSG(handler, "osSetAlarm: handler must not be NULL.");
|
||
if (!alarm || alarm->handler)
|
||
{
|
||
#ifndef SDK_FINALROM
|
||
osPanic("alarm could be already used.");
|
||
#else
|
||
osPanic("");
|
||
#endif
|
||
}
|
||
|
||
enabled = osDisableInterrupts();
|
||
|
||
//---- clear periodic info
|
||
alarm->period = 0;
|
||
|
||
//---- set handler
|
||
alarm->handler = handler;
|
||
alarm->arg = arg;
|
||
|
||
//---- insert alarm
|
||
i_osInsertAlarm(alarm, osGetTick() + tick);
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osSetPeriodicAlarm
|
||
|
||
Description: set periodic alarm
|
||
|
||
Arguments: alarm pointer to alarm to be set
|
||
start origin of the period in absolute tick
|
||
period ticks to count for each period
|
||
handler alarm handler to be called
|
||
arg argument of handler
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osSetPeriodicAlarm(OSAlarm *alarm, OSTick start, OSTick period, OSAlarmHandler handler,
|
||
void *arg)
|
||
{
|
||
u32 enabled;
|
||
|
||
//osPrintf( "**SetPeriodicAlarm s=%llx p=%llx\n", start, period );
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
SDK_ASSERTMSG(handler, "osSetPeriodicAlarm: handler must not be NULL\n");
|
||
SDK_ASSERTMSG(period > 0, "osSetPeriodicAlarm: bad period specified.");
|
||
if (!alarm || alarm->handler)
|
||
{
|
||
#ifndef SDK_FINALROM
|
||
osPanic("alarm could be already used.");
|
||
#else
|
||
osPanic("");
|
||
#endif
|
||
}
|
||
|
||
enabled = osDisableInterrupts();
|
||
|
||
//---- set periodic info
|
||
alarm->period = period;
|
||
alarm->start = start;
|
||
|
||
//---- set handler
|
||
alarm->handler = handler;
|
||
alarm->arg = arg;
|
||
|
||
//---- insert periodic alarm
|
||
i_osInsertAlarm(alarm, 0);
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osCancelAlarm
|
||
|
||
Description: Cancel alarm
|
||
|
||
Arguments: alarm pointer to alarm to be canceled
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osCancelAlarm(OSAlarm *alarm)
|
||
{
|
||
OSAlarm *next;
|
||
u32 enabled;
|
||
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
SDK_ASSERT(alarm);
|
||
|
||
enabled = osDisableInterrupts();
|
||
|
||
if (alarm->handler == NULL)
|
||
{
|
||
(void)osRestoreInterrupts(enabled);
|
||
return;
|
||
}
|
||
|
||
//---- remove alarm
|
||
next = alarm->next;
|
||
if (next == NULL)
|
||
{
|
||
i_osAlarmQueue.tail = alarm->prev;
|
||
}
|
||
else
|
||
{
|
||
next->prev = alarm->prev;
|
||
}
|
||
|
||
if (alarm->prev)
|
||
{
|
||
alarm->prev->next = next;
|
||
}
|
||
else
|
||
{
|
||
i_osAlarmQueue.head = next;
|
||
if (next)
|
||
{
|
||
i_osSetTimer(next);
|
||
}
|
||
}
|
||
|
||
alarm->handler = NULL;
|
||
alarm->period = 0; // not periodic alarm
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: i_osAlarmHandler
|
||
|
||
Description: handler timer interrupt
|
||
|
||
Arguments: arg dummy
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
#include <brom/code32.h>
|
||
asm void i_osAlarmHandler( void* arg )
|
||
{
|
||
INASM_EXTERN( i_osArrangeTimer )
|
||
|
||
stmfd sp!, {r0, lr} /* <20>R<EFBFBD>[<5B><><EFBFBD>X<EFBFBD>^<5E>b<EFBFBD>N<EFBFBD><4E> 8 <20>o<EFBFBD>C<EFBFBD>g<EFBFBD><67><EFBFBD><EFBFBD> */
|
||
bl i_osArrangeTimer
|
||
ldmfd sp!, {r0, lr} /* <20>R<EFBFBD>[<5B><><EFBFBD>X<EFBFBD>^<5E>b<EFBFBD>N<EFBFBD><4E> 8 <20>o<EFBFBD>C<EFBFBD>g<EFBFBD><67><EFBFBD><EFBFBD> */
|
||
bx lr
|
||
}
|
||
#include <brom/codereset.h>
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: i_osArrangeTimer
|
||
|
||
Description: handler timer interrupt. called from i_osAlarmHandler
|
||
|
||
Arguments: None
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void i_osArrangeTimer(void)
|
||
{
|
||
OSTick tick;
|
||
OSAlarm *alarm;
|
||
OSAlarm *next;
|
||
OSAlarmHandler handler;
|
||
|
||
//---- To be timer-irq Disable
|
||
(void)osDisableInterruptID(OSi_ALARM_IE_TIMER_ID);
|
||
|
||
//---- let timer be disable
|
||
osSetTimerControl(OSi_ALARM_TIMER, 0);
|
||
|
||
#ifdef SDK_ARM9
|
||
//---- set check flag timer interrupt
|
||
// osSetIrqCheckFlag(OSi_ALARM_IE_TIMER);
|
||
|
||
#endif // SDK_ARM9
|
||
|
||
tick = osGetTick();
|
||
alarm = i_osAlarmQueue.head;
|
||
|
||
//osPrintf( "**Arrange alarm=%x time=%llx file=%llx\n", alarm, time, alarm->fire );
|
||
|
||
//---- no alarm
|
||
if (alarm == NULL)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//---- not reach to time of top alarm
|
||
if (tick < alarm->fire)
|
||
{
|
||
i_osSetTimer(alarm);
|
||
return;
|
||
}
|
||
|
||
//---- move next alarm to top
|
||
next = alarm->next;
|
||
i_osAlarmQueue.head = next;
|
||
|
||
if (next == NULL)
|
||
{
|
||
i_osAlarmQueue.tail = NULL;
|
||
}
|
||
else
|
||
{
|
||
next->prev = NULL;
|
||
}
|
||
|
||
//---- call user alarm handler
|
||
handler = alarm->handler;
|
||
|
||
if (alarm->period == 0)
|
||
{
|
||
alarm->handler = NULL;
|
||
}
|
||
|
||
if (handler)
|
||
{
|
||
(handler) (alarm->arg);
|
||
}
|
||
|
||
//---- if alarm is periodic, re-inter to list
|
||
if (alarm->period > 0)
|
||
{
|
||
alarm->handler = handler;
|
||
i_osInsertAlarm(alarm, 0);
|
||
}
|
||
|
||
//---- set timer
|
||
if (i_osAlarmQueue.head)
|
||
{
|
||
i_osSetTimer(i_osAlarmQueue.head);
|
||
}
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osSetAlarmTag
|
||
|
||
Description: set tag which is used osCancelAlarms
|
||
|
||
Arguments: alarm alarm to be set tag
|
||
tag tagNo
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osSetAlarmTag(OSAlarm *alarm, u32 tag)
|
||
{
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
SDK_ASSERT(alarm);
|
||
SDK_ASSERTMSG(tag > 0, "osSetAlarmTag: Tag must be >0.");
|
||
|
||
alarm->tag = tag;
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osCancelAlarms
|
||
|
||
Description: cancel alarms which have specified tag
|
||
|
||
Arguments: tag tagNo. to be cancelled. not 0
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osCancelAlarms(u32 tag)
|
||
{
|
||
u32 enabled;
|
||
OSAlarm *alarm;
|
||
OSAlarm *next;
|
||
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
SDK_ASSERTMSG(tag > 0, "OSCancelAlarms: Tag must be >0.");
|
||
|
||
if (tag == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
enabled = osDisableInterrupts();
|
||
|
||
for (alarm = i_osAlarmQueue.head, next = alarm ? alarm->next : NULL;
|
||
alarm; alarm = next, next = alarm ? alarm->next : NULL)
|
||
{
|
||
if (alarm->tag == tag)
|
||
{
|
||
//---- cancel alarm
|
||
osCancelAlarm(alarm);
|
||
}
|
||
}
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
}
|
||
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osCancelAllAlarms
|
||
|
||
Description: cancel all alarms
|
||
|
||
Arguments: None
|
||
|
||
Returns: None.
|
||
*---------------------------------------------------------------------------*/
|
||
void osCancelAllAlarms(void)
|
||
{
|
||
u32 enabled;
|
||
OSAlarm *alarm;
|
||
OSAlarm *next;
|
||
|
||
SDK_ASSERT(i_osUseAlarm);
|
||
enabled = osDisableInterrupts();
|
||
|
||
for (alarm = i_osAlarmQueue.head, next = alarm ? alarm->next : NULL;
|
||
alarm; alarm = next, next = alarm ? alarm->next : NULL)
|
||
{
|
||
//---- cancel alarm
|
||
osCancelAlarm(alarm);
|
||
}
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: i_osGetAlarmQueue
|
||
|
||
Description: get alarm queue
|
||
|
||
Arguments: None
|
||
|
||
Returns: alarm queue.
|
||
*---------------------------------------------------------------------------*/
|
||
struct OSiAlarmQueue *i_osGetAlarmQueue(void)
|
||
{
|
||
return &i_osAlarmQueue;
|
||
}
|
||
|
||
//================================================================================
|
||
// FOR DEBUG
|
||
//================================================================================
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osGetNumberOfAlarm
|
||
|
||
Description: get number of alarm
|
||
|
||
Arguments: None
|
||
|
||
Returns: number of alarm
|
||
*---------------------------------------------------------------------------*/
|
||
int osGetNumberOfAlarm(void)
|
||
{
|
||
OSIntrMode enabled = osDisableInterrupts();
|
||
OSAlarm* p = i_osAlarmQueue.head;
|
||
int num = 0;
|
||
|
||
while(p)
|
||
{
|
||
num ++;
|
||
p = p->next;
|
||
}
|
||
|
||
(void)osRestoreInterrupts(enabled);
|
||
return num;
|
||
}
|
||
|
||
/*---------------------------------------------------------------------------*
|
||
Name: osGetAlarmResource
|
||
|
||
Description: store resources of alarm to specified pointer
|
||
|
||
Arguments: resource pointer to store alarm resources
|
||
|
||
Returns: TRUE ... success (always return this now)
|
||
FALSE ... fail
|
||
*---------------------------------------------------------------------------*/
|
||
BOOL osGetAlarmResource(OSAlarmResource *resource)
|
||
{
|
||
resource->num = osGetNumberOfAlarm();
|
||
|
||
return TRUE;
|
||
}
|
||
|