404 lines
18 KiB
C++
404 lines
18 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2021 - 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
* Niccolò Izzo IU2KIN *
|
|
* Frederik Saraci IU2NRO *
|
|
* Silvano Seva IU2KWO, *
|
|
* Federico Terraneo *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 3 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
|
***************************************************************************/
|
|
|
|
/*
|
|
* startup.cpp
|
|
* STM32 C++ startup.
|
|
* NOTE: for stm32f4 devices ONLY.
|
|
* Supports interrupt handlers in C++ without extern "C"
|
|
* Developed by Terraneo Federico, based on ST startup code.
|
|
* Additionally modified to boot Miosix.
|
|
*/
|
|
|
|
#include "interfaces/arch_registers.h"
|
|
#include "kernel/stage_2_boot.h"
|
|
#include "core/interrupts.h" //For the unexpected interrupt call
|
|
#include <string.h>
|
|
|
|
/**
|
|
* Called by Reset_Handler, performs initialization and calls main.
|
|
* Never returns.
|
|
*/
|
|
void program_startup() __attribute__((noreturn));
|
|
void program_startup()
|
|
{
|
|
//Cortex M3 core appears to get out of reset with interrupts already enabled
|
|
__disable_irq();
|
|
|
|
//SystemInit() is called *before* initializing .data and zeroing .bss
|
|
//Despite all startup files provided by ST do the opposite, there are three
|
|
//good reasons to do so:
|
|
//First, the CMSIS specifications say that SystemInit() must not access
|
|
//global variables, so it is actually possible to call it before
|
|
//Second, when running Miosix with the xram linker scripts .data and .bss
|
|
//are placed in the external RAM, so we *must* call SystemInit(), which
|
|
//enables xram, before touching .data and .bss
|
|
//Third, this is a performance improvement since the loops that initialize
|
|
//.data and zeros .bss now run with the CPU at full speed instead of 8MHz
|
|
SystemInit();
|
|
|
|
// Initialise clock tree for full speed run, since SystemInit by NXP does not.
|
|
//
|
|
// Clock tree configuration:
|
|
// - External clock input: 12.288MHz from HR_C6000 resonator
|
|
// - PLL input divider: 4 -> PLL reference clock is 3.072MHz
|
|
// - PLL multiplier: 39 -> PLL output clock is 119.808MHz
|
|
//
|
|
// - Core and system clock @ 119.808MHz
|
|
// - Bus clock @ 59.904MHz
|
|
// - FlexBus clock @ 29.952MHz
|
|
// - Flash clock @ 23.962MHz
|
|
|
|
SIM->CLKDIV1 = SIM_CLKDIV1_OUTDIV2(1) // Bus clock divider = 2
|
|
| SIM_CLKDIV1_OUTDIV3(3) // FlexBus clock divider = 4
|
|
| SIM_CLKDIV1_OUTDIV4(4); // Flash clock divider = 5
|
|
|
|
MCG->C2 |= MCG_C2_RANGE(2); // Very high frequency range
|
|
OSC->CR |= OSC_CR_ERCLKEN(1); // Enable external reference clock
|
|
|
|
MCG->C6 |= MCG_C6_VDIV0(15); // PLL multiplier set to 39
|
|
MCG->C5 |= MCG_C5_PRDIV0(3) // Divide PLL ref clk by 4
|
|
| MCG_C5_PLLCLKEN0(1); // Enable PLL clock
|
|
|
|
SMC->PMPROT = SMC_PMPROT_AHSRUN(1); // Allow HSRUN mode
|
|
SMC->PMCTRL = SMC_PMCTRL_RUNM(3); // Switch to HSRUN mode
|
|
while((SMC->PMSTAT & 0x80) == 0) ; // Wait till switch to HSRUN is effective
|
|
|
|
MCG->C6 |= MCG_C6_PLLS(1); // Connect PLL to MCG source
|
|
|
|
//These are defined in the linker script
|
|
extern unsigned char _etext asm("_etext");
|
|
extern unsigned char _data asm("_data");
|
|
extern unsigned char _edata asm("_edata");
|
|
extern unsigned char _bss_start asm("_bss_start");
|
|
extern unsigned char _bss_end asm("_bss_end");
|
|
|
|
//Initialize .data section, clear .bss section
|
|
unsigned char *etext=&_etext;
|
|
unsigned char *data=&_data;
|
|
unsigned char *edata=&_edata;
|
|
unsigned char *bss_start=&_bss_start;
|
|
unsigned char *bss_end=&_bss_end;
|
|
memcpy(data, etext, edata-data);
|
|
memset(bss_start, 0, bss_end-bss_start);
|
|
|
|
//Move on to stage 2
|
|
_init();
|
|
|
|
//If main returns, reboot
|
|
NVIC_SystemReset();
|
|
for(;;) ;
|
|
}
|
|
|
|
/**
|
|
* Reset handler, called by hardware immediately after reset
|
|
*/
|
|
void Reset_Handler() __attribute__((__interrupt__, noreturn));
|
|
void Reset_Handler()
|
|
{
|
|
/*
|
|
* Initialize process stack and switch to it.
|
|
* This is required for booting Miosix, a small portion of the top of the
|
|
* heap area will be used as stack until the first thread starts. After,
|
|
* this stack will be abandoned and the process stack will point to the
|
|
* current thread's stack.
|
|
*/
|
|
asm volatile("ldr r0, =_heap_end \n\t"
|
|
"msr psp, r0 \n\t"
|
|
"movw r0, #2 \n\n" //Privileged, process stack
|
|
"msr control, r0 \n\t"
|
|
"isb \n\t":::"r0");
|
|
|
|
program_startup();
|
|
}
|
|
|
|
/**
|
|
* All unused interrupts call this function.
|
|
*/
|
|
extern "C" void Default_Handler()
|
|
{
|
|
unexpectedInterrupt();
|
|
}
|
|
|
|
//System handlers
|
|
void /*__attribute__((weak))*/ Reset_Handler(); //These interrupts are not
|
|
void /*__attribute__((weak))*/ NMI_Handler(); //weak because they are
|
|
void /*__attribute__((weak))*/ HardFault_Handler(); //surely defined by Miosix
|
|
void /*__attribute__((weak))*/ MemManage_Handler();
|
|
void /*__attribute__((weak))*/ BusFault_Handler();
|
|
void /*__attribute__((weak))*/ UsageFault_Handler();
|
|
void /*__attribute__((weak))*/ SVC_Handler();
|
|
void /*__attribute__((weak))*/ DebugMon_Handler();
|
|
void /*__attribute__((weak))*/ PendSV_Handler();
|
|
void /*__attribute__((weak))*/ SysTick_Handler();
|
|
|
|
//Interrupt handlers
|
|
void __attribute__((weak)) DMA0_IRQHandler();
|
|
void __attribute__((weak)) DMA1_IRQHandler();
|
|
void __attribute__((weak)) DMA2_IRQHandler();
|
|
void __attribute__((weak)) DMA3_IRQHandler();
|
|
void __attribute__((weak)) DMA4_IRQHandler();
|
|
void __attribute__((weak)) DMA5_IRQHandler();
|
|
void __attribute__((weak)) DMA6_IRQHandler();
|
|
void __attribute__((weak)) DMA7_IRQHandler();
|
|
void __attribute__((weak)) DMA8_IRQHandler();
|
|
void __attribute__((weak)) DMA9_IRQHandler();
|
|
void __attribute__((weak)) DMA10_IRQHandler();
|
|
void __attribute__((weak)) DMA11_IRQHandler();
|
|
void __attribute__((weak)) DMA12_IRQHandler();
|
|
void __attribute__((weak)) DMA13_IRQHandler();
|
|
void __attribute__((weak)) DMA14_IRQHandler();
|
|
void __attribute__((weak)) DMA15_IRQHandler();
|
|
void __attribute__((weak)) DMA_Error_IRQHandler();
|
|
void __attribute__((weak)) MCM_IRQHandler();
|
|
void __attribute__((weak)) FTF_IRQHandler();
|
|
void __attribute__((weak)) Read_Collision_IRQHandler();
|
|
void __attribute__((weak)) LVD_LVW_IRQHandler();
|
|
void __attribute__((weak)) LLWU_IRQHandler();
|
|
void __attribute__((weak)) WDOG_EWM_IRQHandler();
|
|
void __attribute__((weak)) RNG_IRQHandler();
|
|
void __attribute__((weak)) I2C0_IRQHandler();
|
|
void __attribute__((weak)) I2C1_IRQHandler();
|
|
void __attribute__((weak)) SPI0_IRQHandler();
|
|
void __attribute__((weak)) SPI1_IRQHandler();
|
|
void __attribute__((weak)) I2S0_Tx_IRQHandler();
|
|
void __attribute__((weak)) I2S0_Rx_IRQHandler();
|
|
void __attribute__((weak)) LPUART0_IRQHandler();
|
|
void __attribute__((weak)) UART0_RX_TX_IRQHandler();
|
|
void __attribute__((weak)) UART0_ERR_IRQHandler();
|
|
void __attribute__((weak)) UART1_RX_TX_IRQHandler();
|
|
void __attribute__((weak)) UART1_ERR_IRQHandler();
|
|
void __attribute__((weak)) UART2_RX_TX_IRQHandler();
|
|
void __attribute__((weak)) UART2_ERR_IRQHandler();
|
|
void __attribute__((weak)) Reserved53_IRQHandler();
|
|
void __attribute__((weak)) Reserved54_IRQHandler();
|
|
void __attribute__((weak)) ADC0_IRQHandler();
|
|
void __attribute__((weak)) CMP0_IRQHandler();
|
|
void __attribute__((weak)) CMP1_IRQHandler();
|
|
void __attribute__((weak)) FTM0_IRQHandler();
|
|
void __attribute__((weak)) FTM1_IRQHandler();
|
|
void __attribute__((weak)) FTM2_IRQHandler();
|
|
void __attribute__((weak)) Reserved61_IRQHandler();
|
|
void __attribute__((weak)) RTC_IRQHandler();
|
|
void __attribute__((weak)) RTC_Seconds_IRQHandler();
|
|
void __attribute__((weak)) PIT0_IRQHandler();
|
|
void __attribute__((weak)) PIT1_IRQHandler();
|
|
void __attribute__((weak)) PIT2_IRQHandler();
|
|
void __attribute__((weak)) PIT3_IRQHandler();
|
|
void __attribute__((weak)) PDB0_IRQHandler();
|
|
void __attribute__((weak)) USB0_IRQHandler();
|
|
void __attribute__((weak)) Reserved70_IRQHandler();
|
|
void __attribute__((weak)) Reserved71_IRQHandler();
|
|
void __attribute__((weak)) DAC0_IRQHandler();
|
|
void __attribute__((weak)) MCG_IRQHandler();
|
|
void __attribute__((weak)) LPTMR0_IRQHandler();
|
|
void __attribute__((weak)) PORTA_IRQHandler();
|
|
void __attribute__((weak)) PORTB_IRQHandler();
|
|
void __attribute__((weak)) PORTC_IRQHandler();
|
|
void __attribute__((weak)) PORTD_IRQHandler();
|
|
void __attribute__((weak)) PORTE_IRQHandler();
|
|
void __attribute__((weak)) SWI_IRQHandler();
|
|
void __attribute__((weak)) Reserved81_IRQHandler();
|
|
void __attribute__((weak)) Reserved82_IRQHandler();
|
|
void __attribute__((weak)) Reserved83_IRQHandler();
|
|
void __attribute__((weak)) Reserved84_IRQHandler();
|
|
void __attribute__((weak)) Reserved85_IRQHandler();
|
|
void __attribute__((weak)) Reserved86_IRQHandler();
|
|
void __attribute__((weak)) FTM3_IRQHandler();
|
|
void __attribute__((weak)) DAC1_IRQHandler();
|
|
void __attribute__((weak)) ADC1_IRQHandler();
|
|
|
|
//Stack top, defined in the linker script
|
|
extern char _main_stack_top asm("_main_stack_top");
|
|
|
|
//Interrupt vectors, must be placed @ address 0x00000000
|
|
//The extern declaration is required otherwise g++ optimizes it out
|
|
extern void (* const __Vectors[])();
|
|
void (* const __Vectors[])() __attribute__ ((section(".isr_vector"))) =
|
|
{
|
|
reinterpret_cast<void (*)()>(&_main_stack_top),/* Stack pointer*/
|
|
Reset_Handler,
|
|
NMI_Handler,
|
|
HardFault_Handler,
|
|
MemManage_Handler,
|
|
BusFault_Handler,
|
|
UsageFault_Handler,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
SVC_Handler,
|
|
DebugMon_Handler,
|
|
0,
|
|
PendSV_Handler,
|
|
SysTick_Handler,
|
|
|
|
DMA0_IRQHandler, /* DMA Channel 0 Transfer Complete*/
|
|
DMA1_IRQHandler, /* DMA Channel 1 Transfer Complete*/
|
|
DMA2_IRQHandler, /* DMA Channel 2 Transfer Complete*/
|
|
DMA3_IRQHandler, /* DMA Channel 3 Transfer Complete*/
|
|
DMA4_IRQHandler, /* DMA Channel 4 Transfer Complete*/
|
|
DMA5_IRQHandler, /* DMA Channel 5 Transfer Complete*/
|
|
DMA6_IRQHandler, /* DMA Channel 6 Transfer Complete*/
|
|
DMA7_IRQHandler, /* DMA Channel 7 Transfer Complete*/
|
|
DMA8_IRQHandler, /* DMA Channel 8 Transfer Complete*/
|
|
DMA9_IRQHandler, /* DMA Channel 9 Transfer Complete*/
|
|
DMA10_IRQHandler, /* DMA Channel 10 Transfer Complete*/
|
|
DMA11_IRQHandler, /* DMA Channel 11 Transfer Complete*/
|
|
DMA12_IRQHandler, /* DMA Channel 12 Transfer Complete*/
|
|
DMA13_IRQHandler, /* DMA Channel 13 Transfer Complete*/
|
|
DMA14_IRQHandler, /* DMA Channel 14 Transfer Complete*/
|
|
DMA15_IRQHandler, /* DMA Channel 15 Transfer Complete*/
|
|
DMA_Error_IRQHandler, /* DMA Error Interrupt*/
|
|
MCM_IRQHandler, /* Normal Interrupt*/
|
|
FTF_IRQHandler, /* FTFA Command complete interrupt*/
|
|
Read_Collision_IRQHandler, /* Read Collision Interrupt*/
|
|
LVD_LVW_IRQHandler, /* Low Voltage Detect, Low Voltage Warning*/
|
|
LLWU_IRQHandler, /* Low Leakage Wakeup Unit*/
|
|
WDOG_EWM_IRQHandler, /* WDOG Interrupt*/
|
|
RNG_IRQHandler, /* RNG Interrupt*/
|
|
I2C0_IRQHandler, /* I2C0 interrupt*/
|
|
I2C1_IRQHandler, /* I2C1 interrupt*/
|
|
SPI0_IRQHandler, /* SPI0 Interrupt*/
|
|
SPI1_IRQHandler, /* SPI1 Interrupt*/
|
|
I2S0_Tx_IRQHandler, /* I2S0 transmit interrupt*/
|
|
I2S0_Rx_IRQHandler, /* I2S0 receive interrupt*/
|
|
LPUART0_IRQHandler, /* LPUART0 status/error interrupt*/
|
|
UART0_RX_TX_IRQHandler, /* UART0 Receive/Transmit interrupt*/
|
|
UART0_ERR_IRQHandler, /* UART0 Error interrupt*/
|
|
UART1_RX_TX_IRQHandler, /* UART1 Receive/Transmit interrupt*/
|
|
UART1_ERR_IRQHandler, /* UART1 Error interrupt*/
|
|
UART2_RX_TX_IRQHandler, /* UART2 Receive/Transmit interrupt*/
|
|
UART2_ERR_IRQHandler, /* UART2 Error interrupt*/
|
|
Reserved53_IRQHandler, /* Reserved interrupt 53*/
|
|
Reserved54_IRQHandler, /* Reserved interrupt 54*/
|
|
ADC0_IRQHandler, /* ADC0 interrupt*/
|
|
CMP0_IRQHandler, /* CMP0 interrupt*/
|
|
CMP1_IRQHandler, /* CMP1 interrupt*/
|
|
FTM0_IRQHandler, /* FTM0 fault, overflow and channels interrupt*/
|
|
FTM1_IRQHandler, /* FTM1 fault, overflow and channels interrupt*/
|
|
FTM2_IRQHandler, /* FTM2 fault, overflow and channels interrupt*/
|
|
Reserved61_IRQHandler, /* Reserved interrupt 61*/
|
|
RTC_IRQHandler, /* RTC interrupt*/
|
|
RTC_Seconds_IRQHandler, /* RTC seconds interrupt*/
|
|
PIT0_IRQHandler, /* PIT timer channel 0 interrupt*/
|
|
PIT1_IRQHandler, /* PIT timer channel 1 interrupt*/
|
|
PIT2_IRQHandler, /* PIT timer channel 2 interrupt*/
|
|
PIT3_IRQHandler, /* PIT timer channel 3 interrupt*/
|
|
PDB0_IRQHandler, /* PDB0 Interrupt*/
|
|
USB0_IRQHandler, /* USB0 interrupt*/
|
|
Reserved70_IRQHandler, /* Reserved interrupt 70*/
|
|
Reserved71_IRQHandler, /* Reserved interrupt 71*/
|
|
DAC0_IRQHandler, /* DAC0 interrupt*/
|
|
MCG_IRQHandler, /* MCG Interrupt*/
|
|
LPTMR0_IRQHandler, /* LPTimer interrupt*/
|
|
PORTA_IRQHandler, /* Port A interrupt*/
|
|
PORTB_IRQHandler, /* Port B interrupt*/
|
|
PORTC_IRQHandler, /* Port C interrupt*/
|
|
PORTD_IRQHandler, /* Port D interrupt*/
|
|
PORTE_IRQHandler, /* Port E interrupt*/
|
|
SWI_IRQHandler, /* Software interrupt*/
|
|
Reserved81_IRQHandler, /* Reserved interrupt 81*/
|
|
Reserved82_IRQHandler, /* Reserved interrupt 82*/
|
|
Reserved83_IRQHandler, /* Reserved interrupt 83*/
|
|
Reserved84_IRQHandler, /* Reserved interrupt 84*/
|
|
Reserved85_IRQHandler, /* Reserved interrupt 85*/
|
|
Reserved86_IRQHandler, /* Reserved interrupt 86*/
|
|
FTM3_IRQHandler, /* FTM3 fault, overflow and channels interrupt*/
|
|
DAC1_IRQHandler, /* DAC1 interrupt*/
|
|
ADC1_IRQHandler /* ADC1 interrupt*/
|
|
};
|
|
|
|
#pragma weak DMA0_IRQHandler = Default_Handler
|
|
#pragma weak DMA1_IRQHandler = Default_Handler
|
|
#pragma weak DMA2_IRQHandler = Default_Handler
|
|
#pragma weak DMA3_IRQHandler = Default_Handler
|
|
#pragma weak DMA4_IRQHandler = Default_Handler
|
|
#pragma weak DMA5_IRQHandler = Default_Handler
|
|
#pragma weak DMA6_IRQHandler = Default_Handler
|
|
#pragma weak DMA7_IRQHandler = Default_Handler
|
|
#pragma weak DMA8_IRQHandler = Default_Handler
|
|
#pragma weak DMA9_IRQHandler = Default_Handler
|
|
#pragma weak DMA10_IRQHandler = Default_Handler
|
|
#pragma weak DMA11_IRQHandler = Default_Handler
|
|
#pragma weak DMA12_IRQHandler = Default_Handler
|
|
#pragma weak DMA13_IRQHandler = Default_Handler
|
|
#pragma weak DMA14_IRQHandler = Default_Handler
|
|
#pragma weak DMA15_IRQHandler = Default_Handler
|
|
#pragma weak DMA_Error_IRQHandler = Default_Handler
|
|
#pragma weak MCM_IRQHandler = Default_Handler
|
|
#pragma weak FTF_IRQHandler = Default_Handler
|
|
#pragma weak Read_Collision_IRQHandler = Default_Handler
|
|
#pragma weak LVD_LVW_IRQHandler = Default_Handler
|
|
#pragma weak LLWU_IRQHandler = Default_Handler
|
|
#pragma weak WDOG_EWM_IRQHandler = Default_Handler
|
|
#pragma weak RNG_IRQHandler = Default_Handler
|
|
#pragma weak I2C0_IRQHandler = Default_Handler
|
|
#pragma weak I2C1_IRQHandler = Default_Handler
|
|
#pragma weak SPI0_IRQHandler = Default_Handler
|
|
#pragma weak SPI1_IRQHandler = Default_Handler
|
|
#pragma weak I2S0_Tx_IRQHandler = Default_Handler
|
|
#pragma weak I2S0_Rx_IRQHandler = Default_Handler
|
|
#pragma weak LPUART0_IRQHandler = Default_Handler
|
|
#pragma weak UART0_RX_TX_IRQHandler = Default_Handler
|
|
#pragma weak UART0_ERR_IRQHandler = Default_Handler
|
|
#pragma weak UART1_RX_TX_IRQHandler = Default_Handler
|
|
#pragma weak UART1_ERR_IRQHandler = Default_Handler
|
|
#pragma weak UART2_RX_TX_IRQHandler = Default_Handler
|
|
#pragma weak UART2_ERR_IRQHandler = Default_Handler
|
|
#pragma weak Reserved53_IRQHandler = Default_Handler
|
|
#pragma weak Reserved54_IRQHandler = Default_Handler
|
|
#pragma weak ADC0_IRQHandler = Default_Handler
|
|
#pragma weak CMP0_IRQHandler = Default_Handler
|
|
#pragma weak CMP1_IRQHandler = Default_Handler
|
|
#pragma weak FTM0_IRQHandler = Default_Handler
|
|
#pragma weak FTM1_IRQHandler = Default_Handler
|
|
#pragma weak FTM2_IRQHandler = Default_Handler
|
|
#pragma weak Reserved61_IRQHandler = Default_Handler
|
|
#pragma weak RTC_IRQHandler = Default_Handler
|
|
#pragma weak RTC_Seconds_IRQHandler = Default_Handler
|
|
#pragma weak PIT0_IRQHandler = Default_Handler
|
|
#pragma weak PIT1_IRQHandler = Default_Handler
|
|
#pragma weak PIT2_IRQHandler = Default_Handler
|
|
#pragma weak PIT3_IRQHandler = Default_Handler
|
|
#pragma weak PDB0_IRQHandler = Default_Handler
|
|
#pragma weak USB0_IRQHandler = Default_Handler
|
|
#pragma weak Reserved70_IRQHandler = Default_Handler
|
|
#pragma weak Reserved71_IRQHandler = Default_Handler
|
|
#pragma weak DAC0_IRQHandler = Default_Handler
|
|
#pragma weak MCG_IRQHandler = Default_Handler
|
|
#pragma weak LPTMR0_IRQHandler = Default_Handler
|
|
#pragma weak PORTA_IRQHandler = Default_Handler
|
|
#pragma weak PORTB_IRQHandler = Default_Handler
|
|
#pragma weak PORTC_IRQHandler = Default_Handler
|
|
#pragma weak PORTD_IRQHandler = Default_Handler
|
|
#pragma weak PORTE_IRQHandler = Default_Handler
|
|
#pragma weak SWI_IRQHandler = Default_Handler
|
|
#pragma weak Reserved81_IRQHandler = Default_Handler
|
|
#pragma weak Reserved82_IRQHandler = Default_Handler
|
|
#pragma weak Reserved83_IRQHandler = Default_Handler
|
|
#pragma weak Reserved84_IRQHandler = Default_Handler
|
|
#pragma weak Reserved85_IRQHandler = Default_Handler
|
|
#pragma weak Reserved86_IRQHandler = Default_Handler
|
|
#pragma weak FTM3_IRQHandler = Default_Handler
|
|
#pragma weak DAC1_IRQHandler = Default_Handler
|
|
#pragma weak ADC1_IRQHandler = Default_Handler
|