Example #1
0
// ---------------------------------------------------------------------------
void HAL_Time_Sleep_MicroSeconds(UINT32 uSec)
{
    GLOBAL_LOCK(irq);

    UINT32 current   = HAL_Time_CurrentTicks();
    UINT32 maxDiff = CPU_MicrosecondsToTicks(uSec);

    if(maxDiff <= CPU_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS) maxDiff = 0; 
    else maxDiff -= CPU_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS;  // Subtract overhead

    while(((INT32)(HAL_Time_CurrentTicks() - current)) <= maxDiff);
}
void __section(SectionForFlashOperations) HAL_Time_Sleep_MicroSeconds( UINT32 uSec )
{
    GLOBAL_LOCK(irq);

    UINT32 current   = HAL_Time_CurrentTicks();
    UINT32 maxDiff = CPU_MicrosecondsToTicks( uSec );

    if(maxDiff <= STM32_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS) maxDiff  = 0; 
    else maxDiff -= STM32_SLEEP_USEC_FIXED_OVERHEAD_CLOCKS;

    while(((INT32)(HAL_Time_CurrentTicks() - current)) <= maxDiff);
}
void HAL_COMPLETION::DequeueAndExec()
{
    NATIVE_PROFILE_PAL_ASYNC_PROC_CALL();
    GLOBAL_LOCK(irq);

    HAL_COMPLETION* ptr     = (HAL_COMPLETION*)g_HAL_Completion_List.FirstNode();
    HAL_COMPLETION* ptrNext = (HAL_COMPLETION*)ptr->Next();

    // waitforevents does not have an associated completion, therefore we need to verify
    // than their is a next completion and that the current one has expired.
    if(ptrNext)
    {
        ASSERT(ptr->EventTimeTicks <= HAL_Time_CurrentTicks());
        
        Events_Set(SYSTEM_EVENT_FLAG_SYSTEM_TIMER);

        ptr->Unlink();

        //
        // In case there's no other request to serve, set the next interrupt to be 356 years since last powerup (@25kHz).
        //
        HAL_Time_SetCompare( ptrNext->Next() ? ptrNext->EventTimeTicks : HAL_Completion_IdleValue );

#if defined(_DEBUG)
        ptr->EventTimeTicks = 0;
#endif  // defined(_DEBUG)

        // let the ISR turn on interrupts, if it needs to
        ptr->Execute();
    }
}
void HAL_COMPLETION::EnqueueTicks( UINT64 EventTimeTicks )
{
    NATIVE_PROFILE_PAL_ASYNC_PROC_CALL();
    ASSERT(EventTimeTicks != 0);

    GLOBAL_LOCK(irq);

    this->EventTimeTicks  = EventTimeTicks;
#if defined(_DEBUG)
    this->Start_RTC_Ticks = HAL_Time_CurrentTicks();
#endif

    HAL_COMPLETION* ptr     = (HAL_COMPLETION*)g_HAL_Completion_List.FirstNode();
    HAL_COMPLETION* ptrNext;

    for(;(ptrNext = (HAL_COMPLETION*)ptr->Next()); ptr = ptrNext)
    {
        if(EventTimeTicks < ptr->EventTimeTicks) break;
    }

    g_HAL_Completion_List.InsertBeforeNode( ptr, this );
    
    if(this == g_HAL_Completion_List.FirstNode())
    {
        HAL_Time_SetCompare( EventTimeTicks );
    }
}
void HAL_COMPLETION::EnqueueDelta64( UINT64 uSecFromNow )
{
    NATIVE_PROFILE_PAL_ASYNC_PROC_CALL();
    // grab time first to be closest to now as possible from when this function was called
    UINT64 Now            = HAL_Time_CurrentTicks();
    UINT64 EventTimeTicks = CPU_MicrosecondsToTicks( uSecFromNow );

    EnqueueTicks( Now + EventTimeTicks );
}
void HAL_Time_SetCompare( UINT64 CompareValue )
{
    GLOBAL_LOCK(irq);
    g_nextEvent = CompareValue;
    TIM2->CCR1 = (UINT16)CompareValue; // compare to low bits
    
    if (HAL_Time_CurrentTicks() >= CompareValue) { // missed event
        // trigger immediate interrupt
        TIM2->EGR = TIM_EGR_CC1G; // trigger compare1 event
    }

}
Example #7
0
// ---------------------------------------------------------------------------
void HAL_Time_SetCompare(UINT64 CompareValue)
{
    GLOBAL_LOCK(irq);
    g_nextEvent = CompareValue;
    SYSTIMER->MR[0] = (UINT32)CompareValue;
    SYSTIMER->MCR |= 1; // Enable match interrupt

    if (HAL_Time_CurrentTicks() >= CompareValue) { // Missed event?
        // If so, trigger immediate interrupt
        NVIC->STIR = SYSTIMER_IRQn;
    }
}
Example #8
0
// ---------------------------------------------------------------------------
void systimer_handler(void* param)
{
    GLOBAL_LOCK(irq);

    SYSTIMER->IR = 1; // Clear interrupt

    if (HAL_Time_CurrentTicks() >= g_nextEvent) { // Past event time?
        // Handle it and schedule the next one, if there is one
        HAL_COMPLETION::DequeueAndExec();
    }

}
void Timer_Interrupt (void* param) // timer2 compare event
{
    INTERRUPT_START

    TIM2->SR = ~TIM_SR_CC1IF; // reset interrupt flag
    
    if (HAL_Time_CurrentTicks() >= g_nextEvent) { // handle event
       HAL_COMPLETION::DequeueAndExec(); // this also schedules the next one, if there is one
    }

    INTERRUPT_END
}
Example #10
0
void AT91_GPIO_Driver::PIN_ISR_DESCRIPTOR::HandleDebounce( BOOL edge )
{
    ASSERT_IRQ_MUST_BE_OFF();

    m_completion.Abort();

    UINT8 statusMask = edge ? c_Status_AllowHighEdge  : c_Status_AllowLowEdge ;

    if(m_status & statusMask)
    {
        m_completion.EnqueueTicks( HAL_Time_CurrentTicks() + g_AT91_GPIO_Driver.m_DebounceTicks );
    }
}
static void GenerateNewSslSeed()
{
    UINT8   signature[ 128 ];
    UINT8   IVPtr[ BLOCK_SIZE ];
    BOOL    success;

    UINT64  data[ 2 ]  = { ++g_SSL_SeedData.Config.SeedCounter, HAL_Time_CurrentTicks() };

    memset( &IVPtr[ 0 ], 0, sizeof(IVPtr) );

    success = Crypto_Encrypt( (BYTE*)&g_SSL_SeedData.Config.SslSeedKey[ 0 ], (UINT8*)IVPtr, sizeof(IVPtr), (BYTE*)&data, sizeof(data), signature, sizeof(signature) ) == CRYPTO_SUCCESS ? S_OK : CLR_E_FAIL;

    ASSERT(success);

    ssl_rand_seed(signature, sizeof(signature));

    if(!g_SSL_SeedData.m_completion.IsLinked())
    {
        g_SSL_SeedData.m_completion.EnqueueDelta( 5 * 1000000ul ); // 5 seconds
    }
}
CK_RV NetMFCrypto::GenerateKeyPair(Cryptoki_Session_Context* pSessionCtx, CK_MECHANISM_PTR pMechanism, 
                             CK_ATTRIBUTE_PTR pPublicKeyTemplate , CK_ULONG ulPublicCount, 
                             CK_ATTRIBUTE_PTR pPrivateKeyTemplate, CK_ULONG ulPrivateCount, 
                             CK_OBJECT_HANDLE_PTR phPublicKey    , CK_OBJECT_HANDLE_PTR phPrivateKey)
{
    if(pMechanism->mechanism != CKM_RSA_PKCS_KEY_PAIR_GEN) return CKR_MECHANISM_INVALID;

    KeySeed ks;
    UINT16  d0;
    UINT16  d1;

    UINT8 last = 0;

    for(int i=0; i<SEED_SIZE_BYTES; i++)
    {
        last = (HAL_Time_CurrentTicks() % 256) ^ (last<<3);
        ks.Seed[i] = last;
    }

    if(Crypto_CreateZenithKey((BYTE *)&ks, &d0, &d1) != CRYPTO_SUCCESS)
    {
        return false;
    }
    
    ks.delta[0] = d0;
    ks.delta[1] = d1;

    RSAKey* pPrivate = &s_RSAKeys[s_RSAKeyIndex];
    *phPrivateKey = s_RSAKeyIndex++;

    RSAKey* pPublic = &s_RSAKeys[s_RSAKeyIndex];
    *phPublicKey = s_RSAKeyIndex++;

    Crypto_GeneratePrivateKey(&ks, pPrivate);
    
    Crypto_PublicKeyFromPrivate(pPrivate, pPublic);
    
    return CKR_OK;
}
Example #13
0
void ApplicationEntryPoint()
{
    INT32 timeout       = 20000; // 20 second timeout
    bool  enterBootMode = false;

    // crypto API needs to allocate memory. Initialize simple heap for it. 
    UINT8* BaseAddress;                                 
    UINT32 SizeInBytes;                                 
                                                        
    HeapLocation         ( BaseAddress, SizeInBytes );  
    SimpleHeap_Initialize( BaseAddress, SizeInBytes );  

    g_eng.Initialize( HalSystemConfig.DebuggerPorts[ 0 ] );

    // internal reset and stop check
    enterBootMode = g_PrimaryConfigManager.IsBootLoaderRequired( timeout );

    // ODM defined method to enter bootloader mode
    if(!enterBootMode)
    {
        enterBootMode = WaitForTinyBooterUpload( timeout );
    }
    if(!enterBootMode)   
    {
        if(!g_eng.EnumerateAndLaunch())
        {
            timeout       = -1;
            enterBootMode = true;
        }
    }

    if(enterBootMode)
    {
        LCD_Clear();
        
        hal_fprintf( STREAM_LCD, "TinyBooter v%d.%d.%d.%d\r\n", VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION);
        hal_fprintf( STREAM_LCD, "%s Build Date:\r\n\t%s %s\r\n", HalName, __DATE__, __TIME__ );

        DebuggerPort_Initialize( HalSystemConfig.DebuggerPorts[ 0 ] );

        TinyBooter_OnStateChange( State_EnterBooterMode, NULL );

        DebuggerPort_Flush( HalSystemConfig.DebugTextPort  );
        hal_printf( "TinyBooter v%d.%d.%d.%d\r\n", VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION);
        hal_printf( "%s Build Date: %s %s\r\n", HalName, __DATE__, __TIME__ );
#if defined(__GNUC__)
        hal_printf("GNU Compiler version %d\r\n", __GNUC__);
#elif defined(_ARC)
        hal_printf("ARC Compiler version %d\r\n", _ARCVER);
#elif defined(__ADSPBLACKFIN__)
        hal_printf( "Blackfin Compiler version %d\r\n", __VERSIONNUM__ );
#elif defined(__RENESAS__)
        hal_printf( "Renesas Compiler version %d\r\n", __RENESAS_VERSION__ );
#else
        hal_printf( "ARM Compiler version %d\r\n", __ARMCC_VERSION );
#endif
        DebuggerPort_Flush( HalSystemConfig.DebugTextPort );

        //
        // Send "presence" ping.
        //
        {
            CLR_DBG_Commands::Monitor_Ping cmd;

            cmd.m_source = CLR_DBG_Commands::Monitor_Ping::c_Ping_Source_TinyBooter;

            g_eng.m_controller.SendProtocolMessage( CLR_DBG_Commands::c_Monitor_Ping, WP_Flags::c_NonCritical, sizeof(cmd), (UINT8*)&cmd );
        }
    
        UINT64 ticksStart = HAL_Time_CurrentTicks();

        //
        // Wait for somebody to press a button; if no button press comes in, lauch the image
        //
        do
        {
            const UINT32 c_EventsMask = SYSTEM_EVENT_FLAG_COM_IN |
                                        SYSTEM_EVENT_FLAG_USB_IN |
                                        SYSTEM_EVENT_FLAG_BUTTON;

            UINT32 events = ::Events_WaitForEvents( c_EventsMask, timeout );

            if(events != 0)
            {
                Events_Clear( events );
            }

            if(events & SYSTEM_EVENT_FLAG_BUTTON)
            {
                TinyBooter_OnStateChange( State_ButtonPress, (void*)&timeout );
            }
            
            if(events & (SYSTEM_EVENT_FLAG_COM_IN | SYSTEM_EVENT_FLAG_USB_IN))
            {
                g_eng.ProcessCommands();
            }

            if(LOADER_ENGINE_ISFLAGSET(&g_eng, Loader_Engine::c_LoaderEngineFlag_ValidConnection))
            {
                LOADER_ENGINE_CLEARFLAG(&g_eng, Loader_Engine::c_LoaderEngineFlag_ValidConnection);
                
                TinyBooter_OnStateChange( State_ValidCommunication, (void*)&timeout );

                ticksStart = HAL_Time_CurrentTicks();
            }
            else if((timeout != -1) && (HAL_Time_CurrentTicks()-ticksStart) > CPU_MillisecondsToTicks((UINT32)timeout))
            {
                TinyBooter_OnStateChange( State_Timeout, NULL );
                g_eng.EnumerateAndLaunch();
            }
        } while(true);
    }

    ::CPU_Reset();
}
INT64 HAL_Time_CurrentTime()
{
    return CPU_TicksToTime( HAL_Time_CurrentTicks() );
}
bool WP_Message::Process()
{
    UINT8* buf = (UINT8*)&m_header;
    int    len;

    while(true)
    {
        switch(m_rxState)
        {
        case ReceiveState::Idle:
            return true;

        case ReceiveState::Initialize:
            Release();

            m_rxState = ReceiveState::WaitingForHeader;
            m_pos     = (UINT8*)&m_header;
            m_size    = sizeof(m_header);
            break;

        case ReceiveState::WaitingForHeader:
            if(m_parent->m_phy->ReceiveBytes( m_parent->m_state, m_pos, m_size ) == false) return true;

            //
            // Synch to the start of a message.
            //
            while(true)
            {
                len = sizeof(m_header) - m_size; if(len <= 0) break;

                size_t lenCmp = __min( len, sizeof(m_header.m_signature) );

                if(memcmp( &m_header, MARKER_DEBUGGER_V1, lenCmp ) == 0) break;
                if(memcmp( &m_header, MARKER_PACKET_V1  , lenCmp ) == 0) break;

                memmove( &buf[ 0 ], &buf[ 1 ], len-1 );

                m_pos--;
                m_size++;
            }

            if(len >= sizeof(m_header.m_signature))
            {
                m_rxState = ReceiveState::ReadingHeader;
            }
            break;

        case ReceiveState::ReadingHeader:
            if(m_parent->m_phy->ReceiveBytes( m_parent->m_state, m_pos, m_size ) == false) return true;

            if(m_size == 0)
            {
               m_rxState = ReceiveState::CompleteHeader;
            }
            break;

        case ReceiveState::CompleteHeader:
            {
                bool fBadPacket=true;
                if( VerifyHeader() )
                {
#if defined(BIG_ENDIAN)
                    SwapEndian();
#endif
                    if ( m_parent->m_app->ProcessHeader( m_parent->m_state, this ) )
                    {
                        fBadPacket = false;
                        if(m_header.m_size)
                        {
                            if(m_payload == NULL) // Bad, no buffer...
                            {
                                m_rxState = ReceiveState::Initialize;
                            }
                            else
                            {
                                m_payloadTicks = HAL_Time_CurrentTicks();
                                m_rxState = ReceiveState::ReadingPayload;
                                m_pos     = (UINT8*)m_payload;
                                m_size    = m_header.m_size;
                            }
                        }
                        else
                        {
                            m_rxState = ReceiveState::CompletePayload;
                        }
                    }
                }
                
                if ( fBadPacket )
                {
                    if((m_header.m_flags & WP_Flags::c_NonCritical) == 0)
                    {
                        ReplyBadPacket( WP_Flags::c_BadHeader );
                    }

                    m_rxState = ReceiveState::Initialize;
                }
            }
            break;

        case ReceiveState::ReadingPayload:
            {
                UINT64 curTicks = HAL_Time_CurrentTicks();

                // If the time between consecutive payload bytes exceeds the timeout threshold then assume that
                // the rest of the payload is not coming. Reinitialize to synch on the next header. 

                if(HAL_Time_TicksToTime( curTicks - m_payloadTicks ) < (UINT64)c_PayloadTimeout)
                {
                    m_payloadTicks = curTicks;
                
                    if(m_parent->m_phy->ReceiveBytes( m_parent->m_state, m_pos, m_size ) == false) return true;

                    if(m_size == 0)
                    {
                        m_rxState = ReceiveState::CompletePayload;
                    }
                }
                else
                {
                    m_rxState = ReceiveState::Initialize;
                }
            }
            break;

        case ReceiveState::CompletePayload:
            if(VerifyPayload() == true)
            {
                m_parent->m_app->ProcessPayload( m_parent->m_state, this );
            }
            else
            {
                ReplyBadPacket( WP_Flags::c_BadPayload );
            }

            m_rxState = ReceiveState::Initialize;
            break;


        default:
            return false;
        }
    }
}
Example #16
0
/// Remove this method once dependency on this are gone.
UINT64 Time_CurrentTicks()
{
    return HAL_Time_CurrentTicks();
}
Example #17
0
UINT32 Events_WaitForEvents( UINT32 sleepLevel, UINT32 WakeupSystemEvents, UINT32 Timeout_Milliseconds )
{
    NATIVE_PROFILE_PAL_EVENTS();
    // do NOT call this routine with interrupts disabled,
    // as we can die here, since flags are only set in ISRs
    ASSERT_IRQ_MUST_BE_ON();

    // schedule an interrupt for this far in the future
    // timeout is in milliseconds, convert to Sleep Counts

    UINT64 CountsRemaining = CPU_MillisecondsToTicks( Timeout_Milliseconds );

#if defined(HAL_PROFILE_ENABLED)
    Events_WaitForEvents_Calls++;
#endif

    {
        GLOBAL_LOCK(irq);

        // then check to make sure the events haven't happened on the way in
        // we must do this before we sleep!

        UINT64 Expire           = HAL_Time_CurrentTicks() + CountsRemaining;
        BOOL   RunContinuations = TRUE;

        while(true)
        {
            UINT32 Events = Events_MaskedRead( WakeupSystemEvents ); if(Events) return Events;

            if(Expire <= HAL_Time_CurrentTicks()) return 0;


            // first check and possibly run any continuations
            // but only if we have slept after stalling
            if(RunContinuations && !SystemState_QueryNoLock( SYSTEM_STATE_NO_CONTINUATIONS ))
            {
                // restore interrupts before running a continuation
                irq.Release();

                // if we stall on time, don't check again until after we sleep
                RunContinuations = HAL_CONTINUATION::Dequeue_And_Execute();

                irq.Acquire();
            }
            else
            {
                // try stalled continuations again after sleeping
                RunContinuations = TRUE;

                //lcd_printf("\fSleep=%6lld   ", CountsRemaining);
                //lcd_printf(  "Events=%08x", Events_MaskedRead(0xffffffff));

#if defined(HAL_TIMEWARP)
                if(s_timewarp_lastButton < HAL_Time_TicksToTime( HAL_Time_CurrentTicks() ))
                {
                    CountsRemaining = Expire - HAL_Time_CurrentTicks();

                    if(CountsRemaining > 0)
                    {
                        s_timewarp_compensate += (CountsRemaining * 10*1000*1000) / CPU_TicksPerSecond();
                    }
                    return 0;
                }
#endif

                ASSERT_IRQ_MUST_BE_OFF();

                HAL_COMPLETION::WaitForInterrupts( Expire, sleepLevel, WakeupSystemEvents );

                irq.Probe(); // See if we have to serve any pending interrupts.                
            }
        }
    }
}
Example #18
0
BOOL USART_Driver::AddCharToRxBuffer( int ComPortNum, char c )
{
    ASSERT_IRQ_MUST_BE_OFF();

    if((ComPortNum < 0) || (ComPortNum >= TOTAL_USART_PORT)) return FALSE;

    HAL_USART_STATE& State = Hal_Usart_State[ComPortNum];

    if (USART_FLAG_STATE(State, HAL_USART_STATE::c_TX_SWFLOW_CTRL))

    {
        switch( c )
        {
            case XOFF:
                State.TicksStartTxXOFF = HAL_Time_CurrentTicks();
                CLEAR_USART_FLAG(State, HAL_USART_STATE::c_TX_XON_STATE);
                return TRUE;
            case XON:
                SET_USART_FLAG(State, HAL_USART_STATE::c_TX_XON_STATE);
                return TRUE;
        }
    }


    {
        GLOBAL_LOCK(irq);

        UINT8* Dst = State.RxQueue.Push();

        if(Dst)
        {
            *Dst = c;

            if( State.RxQueue.NumberOfElements() >= State.RxBufferHighWaterMark )
            {
                if( USART_FLAG_STATE(State, HAL_USART_STATE::c_RX_SWFLOW_CTRL) )
                {
                    // Set our XOFF state
                    SendXOFF( ComPortNum, XOFF_FLAG_FULL );
                }
                if( USART_FLAG_STATE(State, HAL_USART_STATE::c_RX_HWFLOW_CTRL) )
                {
                    // Hold off receiving characters (should pull HW handshake automatically)
                    CPU_USART_RxBufferFullInterruptEnable(ComPortNum, FALSE);
                }
            }
        }
        
        else
        {
            SetEvent( ComPortNum, USART_EVENT_ERROR_RXOVER );
                
#if !defined(BUILD_RTM)
            lcd_printf("\fBuffer OVFLW\r\n");
            hal_printf("Buffer OVFLW\r\n");
#endif
            return FALSE;
        }
    }

    SetEvent( ComPortNum, USART_EVENT_DATA_CHARS );

    Events_Set( SYSTEM_EVENT_FLAG_COM_IN );

    return TRUE;
}
Example #19
0
int USART_Driver::Write( int ComPortNum, const char* Data, size_t size )
{
    NATIVE_PROFILE_PAL_COM();
    int         totWrite = 0;
    const char* ptr      = Data;
    int j;

    if((ComPortNum < 0) || (ComPortNum >= TOTAL_USART_PORT)) {ASSERT(FALSE); return -1;}
    if(0    == size                                        )                 return -1;
    if(NULL == Data                                        ) {ASSERT(FALSE); return -1;}

    HAL_USART_STATE& State = Hal_Usart_State[ComPortNum];

    if (IS_POWERSAVE_ENABLED(State) || (!IS_USART_INITIALIZED(State)))return -1;

    if (USART_FLAG_STATE(State, HAL_USART_STATE::c_TX_SWFLOW_CTRL) && (!USART_FLAG_STATE(State, HAL_USART_STATE::c_TX_XON_STATE)))
    {   
        // A timeout is used for XOFF incase the XOFF value was lost or never sent.
        // USART_TX_XOFF_TIMEOUT_TICKS is defined in the platform selector file
        if((USART_TX_XOFF_TIMEOUT_INFINITE != USART_TX_XOFF_TIMEOUT_TICKS              ) &&
           (HAL_Time_CurrentTicks() - State.TicksStartTxXOFF) > USART_TX_XOFF_TIMEOUT_TICKS)
        {
            SET_USART_FLAG(State, HAL_USART_STATE::c_TX_XON_STATE);
        }
        else
        {
            return 0;
        }
    }

    // loop twice if needed because of our implementaition of a circular buffered QUEUE
    for(j=0; (j < 2) && (totWrite < size); j++)
    {
        // Keep interrupts off to keep queue access atomic
        GLOBAL_LOCK(irq);
        UINT8 *Dst;
        size_t nWritten;

        nWritten = size - totWrite;
        Dst = State.TxQueue.Push( nWritten );
        if( Dst != NULL )
        {
            memcpy(Dst, ptr, nWritten); // Move characters to transmit queue from buffer
            totWrite += nWritten;
            ptr      += nWritten;
        }
        else if(!USART_FLAG_STATE(State, HAL_USART_STATE::c_RX_HWFLOW_CTRL))
        {
            SetEvent( ComPortNum, USART_EVENT_ERROR_TXFULL );
            break;
        }
    }

    // we need to be atomic on PowerSave/USART_TxBufferEmptyInterruptEnable
    // since it gets set/cleared from ISR, and disables the clock, but
    // USART_TxBufferEmptyInterruptEnable turns the clock back on!
    // We don't want to turn on the USART clock if in power save mode
    {
        GLOBAL_LOCK(irq);

        if(size && !IS_POWERSAVE_ENABLED(State))
        {
            // if we added chars, enable interrupts so characters will actually start flowing
            // we could do this early, then we race each iteration, and could cause a stall,
            // so we do this once to be efficient in the common case (buffer has room for all chars)
            CPU_USART_TxBufferEmptyInterruptEnable( ComPortNum, TRUE );
        }
    }

    return totWrite;
}