diff --git a/hal/gaisler/ambapp.c b/hal/gaisler/ambapp.c index 14bc28d69..490004b44 100644 --- a/hal/gaisler/ambapp.c +++ b/hal/gaisler/ambapp.c @@ -89,7 +89,7 @@ static int ambapp_apbFind(addr_t apb, ambapp_dev_t *dev, unsigned int *instance) hal_spinlockSet(&ambapp_common.lock, &sc); /* Map bridge PnP */ - apbdev = pmap_halMap((addr_t)apbdev, (void *)ambapp_common.apbpnp, SIZE_PAGE, PGHD_READ | PGHD_PRESENT); + apbdev = pmap_halMap((addr_t)apbdev, (void *)ambapp_common.apbpnp, SIZE_PAGE, PGHD_READ | PGHD_NOT_CACHED | PGHD_PRESENT); for (i = 0; i < AMBAPP_APB_NSLAVES; i++) { id = apbdev[i].id; @@ -230,6 +230,7 @@ void ambapp_init(void) { hal_spinlockCreate(&ambapp_common.lock, "ambapp_common.lock"); - ambapp_common.ahbpnp = (ptr_t)_pmap_halMap(AMBAPP_AHB_MSTR, NULL, SIZE_PAGE, PGHD_READ | PGHD_PRESENT); - ambapp_common.apbpnp = (ptr_t)_pmap_halMap(NULL, NULL, SIZE_PAGE, PGHD_READ | PGHD_PRESENT); + /* NOTE: on GR740 uncacheable areas (AMBA PnP) must be mapped as such */ + ambapp_common.ahbpnp = (ptr_t)_pmap_halMap(AMBAPP_AHB_MSTR, NULL, SIZE_PAGE, PGHD_READ | PGHD_NOT_CACHED | PGHD_PRESENT); + ambapp_common.apbpnp = (ptr_t)_pmap_halMap(NULL, NULL, SIZE_PAGE, PGHD_READ | PGHD_NOT_CACHED | PGHD_PRESENT); } diff --git a/hal/sparcv8leon/exceptions.c b/hal/sparcv8leon/exceptions.c index 280a169e3..e0f28ecb6 100644 --- a/hal/sparcv8leon/exceptions.c +++ b/hal/sparcv8leon/exceptions.c @@ -142,7 +142,7 @@ __attribute__((noreturn)) static void exceptions_defaultHandler(unsigned int n, hal_consolePrint(ATTR_BOLD, buff); #ifdef NDEBUG - // hal_cpuReboot(); + hal_cpuReboot(); #endif for (;;) { diff --git a/hal/sparcv8leon/gaisler/Makefile b/hal/sparcv8leon/gaisler/Makefile index f72ff9675..9ebecd2aa 100644 --- a/hal/sparcv8leon/gaisler/Makefile +++ b/hal/sparcv8leon/gaisler/Makefile @@ -1,7 +1,7 @@ # # Makefile for Phoenix-RTOS kernel (SPARCV8 LEON3 HAL) # -# Copyright 2023 Phoenix Systems +# Copyright 2023-2025 Phoenix Systems # # %LICENSE% # @@ -16,6 +16,8 @@ ifeq ($(TARGET_SUBFAMILY), gr712rc) OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/gaisler/, irqmp.o) else ifeq ($(TARGET_SUBFAMILY), gr716) OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/gaisler/, irqamp.o) +else ifeq ($(TARGET_SUBFAMILY), gr740) + OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/gaisler/, irqamp.o l2cache.o) else ifeq ($(TARGET_SUBFAMILY), generic) OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/gaisler/, irqmp.o) endif diff --git a/hal/sparcv8leon/gaisler/console.c b/hal/sparcv8leon/gaisler/console.c index 77ed360b1..e8b6bda86 100644 --- a/hal/sparcv8leon/gaisler/console.c +++ b/hal/sparcv8leon/gaisler/console.c @@ -76,6 +76,26 @@ static void console_iomuxCfg(void) gaisler_setIomuxCfg(CONSOLE_RX, 0x1, 0, 0); } +#elif defined(__CPU_GR740) + +static void console_cguClkEnable(void) +{ + _gr740_cguClkEnable(CONSOLE_CGU); +} + + +static int console_cguClkStatus(void) +{ + return _gr740_cguClkStatus(CONSOLE_CGU); +} + + +static void console_iomuxCfg(void) +{ + gaisler_setIomuxCfg(CONSOLE_TX, iomux_alternateio, 0, 0); + gaisler_setIomuxCfg(CONSOLE_RX, iomux_alternateio, 0, 0); +} + #else static void console_cguClkEnable(void) @@ -146,6 +166,7 @@ void _hal_consoleInit(void) halconsole_common.uart = _pmap_halMapDevice(PAGE_ALIGN(UART_CONSOLE_BASE), PAGE_OFFS(UART_CONSOLE_BASE), SIZE_PAGE); *(halconsole_common.uart + uart_ctrl) = 0; + hal_cpuDataStoreBarrier(); console_iomuxCfg(); @@ -158,6 +179,7 @@ void _hal_consoleInit(void) (void)*(halconsole_common.uart + uart_data); } *(halconsole_common.uart + uart_scaler) = _hal_consoleCalcScaler(CONSOLE_BAUDRATE); + hal_cpuDataStoreBarrier(); *(halconsole_common.uart + uart_ctrl) = TX_EN; hal_cpuDataStoreBarrier(); } diff --git a/hal/sparcv8leon/gaisler/gaisler.h b/hal/sparcv8leon/gaisler/gaisler.h index 9a122106a..d669adbbf 100644 --- a/hal/sparcv8leon/gaisler/gaisler.h +++ b/hal/sparcv8leon/gaisler/gaisler.h @@ -29,4 +29,7 @@ void gaisler_cpuHalt(void); void hal_cpuStartCores(void); +void hal_timerWdogReboot(void); + + #endif diff --git a/hal/sparcv8leon/gaisler/gr740/Makefile b/hal/sparcv8leon/gaisler/gr740/Makefile new file mode 100644 index 000000000..9d7c2a4ec --- /dev/null +++ b/hal/sparcv8leon/gaisler/gr740/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for Phoenix-RTOS kernel (sparcv8leon-gr740 HAL) +# +# Copyright 2025 Phoenix Systems +# + +include hal/tlb/Makefile + +OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/gaisler/$(TARGET_SUBFAMILY)/, _init.o gr740.o) diff --git a/hal/sparcv8leon/gaisler/gr740/_init.S b/hal/sparcv8leon/gaisler/gr740/_init.S new file mode 100644 index 000000000..d484f4311 --- /dev/null +++ b/hal/sparcv8leon/gaisler/gr740/_init.S @@ -0,0 +1,286 @@ +/* + * Phoenix-RTOS + * + * Operating system kernel + * + * Low level initialization + * + * Copyright 2023-2025 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#define __ASSEMBLY__ + +#include +#include +#include + +#include "hal/sparcv8leon/srmmu.h" + +#define ADDR_SDRAM 0x00000000 + +#define PHY_ADDR(va) ((va) - VADDR_KERNEL + ADDR_SDRAM) + +#define ADDR_CTXTAB PHY_ADDR(pmap_common) +#define ADDR_PDIR1 (ADDR_CTXTAB + 0x400) +#define ADDR_PDIR2 (ADDR_PDIR1 + 0x400) +#define ADDR_PDIR3 (ADDR_CTXTAB + 0x1000) + +#define VADDR_SYSPAGE _hal_syspageCopied +#define PADDR_SYSPAGE PHY_ADDR(VADDR_SYSPAGE) + +#define VADDR_STACK (pmap_common + 7 * SIZE_PAGE) + + +.extern syspage +.extern _end + + +.macro calc_ptd paddr out + srl \paddr, 6, \out + sll \out, 2, \out + or \out, PAGE_DESCR, \out +.endm + + +.macro set_page_align addr out + sethi %hi(\addr), \out + srl \out, 12, \out + sll \out, 12, \out +.endm + + +.section ".text" +.align 4 +.global _init +_init: + wr %g0, %wim + nop + nop + nop + + wr %g0, PSR_S, %psr + + /* Flush TLB, I and D cache */ + sta %g0, [%g0] ASI_FLUSH_ALL + + /* Get CPU ID */ + rd %asr17, %g1 + srl %g1, 28, %g1 + cmp %g1, %g0 + bnz _init_core + nop + + /* %g2 = syspage pa (from plo) */ + set PHY_ADDR(syspage), %g3 + set VADDR_SYSPAGE, %g1 + + /* store VADDR_SYSPAGE in syspage */ + st %g1, [%g3] + + set PHY_ADDR(relOffs), %g4 + /* %l0 = offset between VADDR_SYSPAGE and syspage pa */ + sub %g1, %g2, %l0 + st %l0, [%g4] + + set PADDR_SYSPAGE, %g3 + /* calculate pa of syspage end */ + ld [%g2 + 4], %l1 /* %l1 = syspage->size */ + add %g2, %l1, %l2 /* %l2 = plo syspage end */ + +syspage_cpy: + ld [%g2], %l0 + st %l0, [%g3] + add %g2, 4, %g2 + cmp %g2, %l2 + bl syspage_cpy + add %g3, 4, %g3 + + /* Clear page tables */ + set ADDR_CTXTAB, %g2 /* %g2 = &pmap_common.pdir1 (phy) */ + set 0x5000, %g3 + clr %g1 + +clear_pdirs: + subcc %g3, 8, %g3 + std %g0, [%g2] + bnz clear_pdirs + add %g2, 8, %g2 + + /* Set context table pointer */ + set ADDR_CTXTAB, %g3 + set ADDR_PDIR1, %g4 + calc_ptd %g4, %g4 + st %g4, [%g3] + srl %g3, 6, %g3 + sll %g3, 2, %g3 + set MMU_CTX_PTR, %g4 + sta %g3, [%g4] ASI_MMU_REGS + + /* Choose context 0 */ + set MMU_CTX, %g3 + sta %g0, [%g3] ASI_MMU_REGS + + /* Set up page table level 1 */ + + set ADDR_PDIR1, %g1 /* %g1 = pmap_common.pdir1 */ + set ADDR_PDIR2, %g3 /* %g3 = pmap_common.pdir2 */ + + /* V 0x00000000 -> P 0x00000000 */ + sethi %hi(ADDR_SDRAM), %g2 + srl %g2, 24, %g2 + sll %g2, 2, %g2 + add %g1, %g2, %g4 /* %g4 = &pmap_common.pdir1[(ADDR_SDRAM >> 24)] */ + calc_ptd %g3, %g5 + st %g5, [%g4] /* pmap_common.pdir1[(ADDR_SDRAM >> 24)] = PTD(&pmap_common.pdir2[0]) */ + + /* V 0xc0000000 -> P 0x00000000 */ + sethi %hi(VADDR_KERNEL), %g2 + srl %g2, 24, %g2 + sll %g2, 2, %g2 + /* %g1 = &pmap_common.pdir1 */ + add %g1, %g2, %g4 /* %g4 = &pmap_common.pdir1[(VADDR_KERNEL >> 24)] */ + st %g5, [%g4] /* pmap_common.pdir1[(VADDR_KERNEL >> 24)] = PTD(&pmap_common.pdir2[0]) */ + + /* Setup page tables level 2 & 3 */ + + set ADDR_PDIR2, %g1 /* %g1 = pmap_common.pdir2 */ + set ADDR_PDIR3, %g2 /* %g2 = pmap_common.pdir3 */ + sethi %hi(0x40000), %g3 /* 0x40000 = 256KB */ + sethi %hi(ADDR_SDRAM), %g6 + or %g1, 0x100, %g5 /* end = 64 entries */ + set 0x1000, %l0 + + /* for (int i = 0; i < 64; i++) { + * pmap_common.pdir2[i] = PTD(&pmap_common.pdir3[i][0]); + * for (int j = 0; j < 64; j++) + * pmap_common.pdir3[i][j] = PTE(0x40000000 + (i * 256KB) + (j * 4KB)); + * } + */ + +set_pdir2: + add %g6, %g3, %g7 + calc_ptd %g2, %l2 + st %l2, [%g1] /* pmap_common.pdir2[i] = &pmap_common.pdir3[i][0] */ +set_pdir3: + srl %g6, 12, %l1 + sll %l1, 8, %l1 + or %l1, ((1 << 7) | (0x7 << 2) | PAGE_ENTRY), %l1 /* cacheable, supervisor RWX */ + st %l1, [%g2] + add %g6, %l0, %g6 + cmp %g6, %g7 + bne set_pdir3 + add %g2, 4, %g2 + + add %g1, 4, %g1 + cmp %g1, %g5 + bne set_pdir2 + nop + + /* Enable MMU */ + mov 0x1, %g1 + sta %g1, [%g0] ASI_MMU_REGS + + /* Set up trap table */ + sethi %hi(_trap_table), %g1 + wr %g1, %tbr + + /* Set PSR to "supervisor", enable traps, disable interrupts, set CWP to 0 */ + mov %psr, %g1 + or %g1, (PSR_ET | PSR_S | PSR_PIL), %g1 + andn %g1, (PSR_CWP), %g1 + wr %g1, %psr + nop + nop + nop + + wr %g0, 0x2, %wim + + /* Set stack pointer */ + clr %fp + set VADDR_STACK, %sp + sub %sp, 0x60, %sp + + set main, %g1 + call %g1 + mov %g0, %g1 +.size _init, . - _init + + +.global _init_core +.type _init_core, #function +_init_core: + /* Flush and enable cache */ + set 0x81000f, %g1 + sta %g1, [%g0] ASI_CACHE_CTRL + + /* Set context table pointer */ + set ADDR_CTXTAB, %g3 + set ADDR_PDIR1, %g4 + calc_ptd %g4, %g4 + st %g4, [%g3] + srl %g3, 6, %g3 + sll %g3, 2, %g3 + set MMU_CTX_PTR, %g4 + sta %g3, [%g4] ASI_MMU_REGS + + /* Choose context 0 */ + set MMU_CTX, %g3 + sta %g0, [%g3] ASI_MMU_REGS + + clr %fp + set VADDR_STACK, %sp + + /* Get CPU ID */ + rd %asr17, %g1 + srl %g1, 28, %g1 + sll %g1, 12, %g1 + add %sp, %g1, %sp + sub %sp, 0x60, %sp + + /* Enable MMU */ + mov 0x1, %g1 + sta %g1, [%g0] ASI_MMU_REGS + + /* Set up trap table */ + sethi %hi(_trap_table), %g1 + wr %g1, %tbr + + mov %psr, %g1 + or %g1, (PSR_ET | PSR_S | PSR_PIL), %g1 + andn %g1, (PSR_CWP), %g1 + wr %g1, %psr + + /* Switch to virtual memory */ + sethi %hi(_vmem_switch), %g1 + jmp %g1 + %lo(_vmem_switch) + wr %g0, 0x2, %wim + +_vmem_switch: + call hal_cpuInitCore + nop + + /* Enable interrupts and wait to be scheduled */ + mov %psr, %g1 + andn %g1, (PSR_PIL), %g1 + wr %g1, %psr + nop + nop + nop + +_init_core_loop: + wr %g0, %asr19 + ba _init_core_loop + nop +.size _init_core, . - _init_core + + +.section ".bss" +.align 4 +_hal_syspageCopied: + .zero 0x400 +.size _hal_syspageCopied, . - _hal_syspageCopied diff --git a/hal/sparcv8leon/gaisler/gr740/config.h b/hal/sparcv8leon/gaisler/gr740/config.h new file mode 100644 index 000000000..563626199 --- /dev/null +++ b/hal/sparcv8leon/gaisler/gr740/config.h @@ -0,0 +1,42 @@ +/* + * Phoenix-RTOS + * + * Operating system kernel + * + * Configuration file for sparcv8leon-gr740 + * + * Copyright 2024 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef _HAL_CONFIG_H_ +#define _HAL_CONFIG_H_ + + +#ifndef __ASSEMBLY__ + + +#include "gr740.h" + +#include "include/arch/sparcv8leon/syspage.h" +#include "include/syspage.h" + +#define HAL_NAME_PLATFORM "SPARCv8 LEON3-GR740" + +#define ADDR_RAM 0x00000000 +#define SIZE_RAM (128 * 1024 * 1024) /* 128 MB */ + +#define L2CACHE_CTRL_BASE ((void *)0xf0000000) + +#endif /* __ASSEMBLY__ */ + + +#define NWINDOWS 8 +#define NUM_CPUS 4 + + +#endif diff --git a/hal/sparcv8leon/gaisler/gr740/gr740.c b/hal/sparcv8leon/gaisler/gr740/gr740.c new file mode 100644 index 000000000..be662c395 --- /dev/null +++ b/hal/sparcv8leon/gaisler/gr740/gr740.c @@ -0,0 +1,240 @@ +/* + * Phoenix-RTOS + * + * Operating system kernel + * + * HAL internal functions for sparcv8leon-gr740 + * + * Copyright 2025 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include + +#include "hal/cpu.h" +#include "hal/interrupts.h" +#include "hal/tlb/tlb.h" +#include "hal/sparcv8leon/gaisler/l2cache.h" + +#include "include/arch/sparcv8leon/gr740/gr740.h" + +#include "config.h" + +#define L2C_BASE ((addr_t)0xf0000000) + +/* Clock gating unit */ + +#define CGU_BASE ((void *)0xffa04000) + +enum { + cgu_unlock = 0, /* Unlock register : 0x00 */ + cgu_clk_en, /* Clock enable register : 0x04 */ + cgu_core_reset, /* Core reset register : 0x08 */ + cgu_override, /* Override register : 0x0C */ +}; + +/* I/O & PLL configuration registers */ + +#define GRGPREG_BASE ((void *)0xffa0b000) + +enum { + ftmfunc = 0, /* FTMCTRL function enable register : 0x00 */ + altfunc, /* Alternate function enable register : 0x04 */ + lvdsmclk, /* LVDS and mem CLK pad enable register : 0x08 */ + pllnewcfg, /* PLL new configuration register : 0x0C */ + pllrecfg, /* PLL reconfigure command register : 0x10 */ + pllcurcfg, /* PLL current configuration register : 0x14 */ + drvstr1, /* Drive strength register 1 : 0x18 */ + drvstr2, /* Drive strength register 2 : 0x1C */ + lockdown, /* Configuration lockdown register : 0x20 */ +}; + + +static struct { + spinlock_t pltctlSp; + volatile u32 *cguBase; + volatile u32 *grgpregBase; + intr_handler_t tlbIrqHandler; +} gr740_common; + + +volatile u32 hal_cpusStarted; + + +void hal_cpuHalt(void) +{ + /* clang-format off */ + __asm__ volatile ("wr %g0, %asr19"); + /* clang-format on */ +} + + +void hal_cpuInitCore(void) +{ + hal_tlbInitCore(hal_cpuGetID()); + /* Enable cycle counter */ + /* clang-format off */ + __asm__ volatile ("wr %g0, %asr22"); + /* clang-format on */ + hal_cpuAtomicInc(&hal_cpusStarted); +} + + +void _hal_cpuInit(void) +{ + hal_cpusStarted = 0; + hal_cpuInitCore(); + hal_cpuStartCores(); + + while (hal_cpusStarted != NUM_CPUS) { + } + + l2c_init(L2C_BASE); + l2c_flushRange(l2c_inv_all, 0, 0); +} + + +int gaisler_setIomuxCfg(u8 pin, u8 opt, u8 pullup, u8 pulldn) +{ + if (pin > 21) { + return -1; + } + + switch (opt) { + case iomux_gpio: + *(gr740_common.grgpregBase + ftmfunc) &= ~(1 << pin); + *(gr740_common.grgpregBase + altfunc) &= ~(1 << pin); + break; + + case iomux_alternateio: + *(gr740_common.grgpregBase + ftmfunc) &= ~(1 << pin); + *(gr740_common.grgpregBase + altfunc) |= 1 << pin; + break; + + case iomux_promio: + *(gr740_common.grgpregBase + ftmfunc) |= 1 << pin; + break; + + default: + return -1; + } + + return 0; +} + +/* CGU setup - section 25.2 GR740 manual */ + +void _gr740_cguClkEnable(u32 device) +{ + u32 msk = 1 << device; + + *(gr740_common.cguBase + cgu_unlock) |= msk; + *(gr740_common.cguBase + cgu_core_reset) |= msk; + *(gr740_common.cguBase + cgu_clk_en) |= msk; + *(gr740_common.cguBase + cgu_clk_en) &= ~msk; + *(gr740_common.cguBase + cgu_core_reset) &= ~msk; + *(gr740_common.cguBase + cgu_clk_en) |= msk; + *(gr740_common.cguBase + cgu_unlock) &= ~msk; +} + + +void _gr740_cguClkDisable(u32 device) +{ + u32 msk = 1 << device; + + *(gr740_common.cguBase + cgu_unlock) |= msk; + *(gr740_common.cguBase + cgu_clk_en) &= ~msk; + *(gr740_common.cguBase + cgu_unlock) &= ~msk; +} + + +int _gr740_cguClkStatus(u32 device) +{ + u32 msk = 1 << device; + + return (*(gr740_common.cguBase + cgu_clk_en) & msk) ? 1 : 0; +} + + +void hal_wdgReload(void) +{ +} + + +int hal_platformctl(void *ptr) +{ + platformctl_t *data = (platformctl_t *)ptr; + spinlock_ctx_t sc; + int ret = -1; + + hal_spinlockSet(&gr740_common.pltctlSp, &sc); + + switch (data->type) { + case pctl_cguctrl: + if (data->action == pctl_set) { + if (data->task.cguctrl.v.state == disable) { + _gr740_cguClkDisable(data->task.cguctrl.cgudev); + } + else { + _gr740_cguClkEnable(data->task.cguctrl.cgudev); + } + ret = 0; + } + else if (data->action == pctl_get) { + data->task.cguctrl.v.stateVal = _gr740_cguClkStatus(data->task.cguctrl.cgudev); + ret = 0; + } + break; + + case pctl_iomux: + if (data->action == pctl_set) { + ret = gaisler_setIomuxCfg(data->task.iocfg.pin, data->task.iocfg.opt, data->task.iocfg.pullup, data->task.iocfg.pulldn); + } + break; + + case pctl_ambapp: + if (data->action == pctl_get) { + ret = ambapp_findSlave(data->task.ambapp.dev, data->task.ambapp.instance); + } + break; + + case pctl_reboot: + if ((data->action == pctl_set) && (data->task.reboot.magic == PCTL_REBOOT_MAGIC)) { + hal_cpuReboot(); + } + break; + + default: + break; + } + hal_spinlockClear(&gr740_common.pltctlSp, &sc); + + return ret; +} + + +void hal_cpuReboot(void) +{ + hal_timerWdogReboot(); +} + + +void _hal_platformInit(void) +{ + hal_spinlockCreate(&gr740_common.pltctlSp, "pltctl"); + + gr740_common.cguBase = _pmap_halMapDevice(PAGE_ALIGN(CGU_BASE), PAGE_OFFS(CGU_BASE), SIZE_PAGE); + gr740_common.grgpregBase = _pmap_halMapDevice(PAGE_ALIGN(GRGPREG_BASE), PAGE_OFFS(GRGPREG_BASE), SIZE_PAGE); + + gr740_common.tlbIrqHandler.f = hal_tlbIrqHandler; + gr740_common.tlbIrqHandler.n = TLB_IRQ; + gr740_common.tlbIrqHandler.data = NULL; + + hal_interruptsSetHandler(&gr740_common.tlbIrqHandler); + + ambapp_init(); +} diff --git a/hal/sparcv8leon/gaisler/gr740/gr740.h b/hal/sparcv8leon/gaisler/gr740/gr740.h new file mode 100644 index 000000000..640053bdf --- /dev/null +++ b/hal/sparcv8leon/gaisler/gr740/gr740.h @@ -0,0 +1,53 @@ +/* + * Phoenix-RTOS + * + * Operating system kernel + * + * HAL internal functions for sparcv8leon-gr740 + * + * Copyright 2025 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef _HAL_GR740_H_ +#define _HAL_GR740_H_ + + +#ifndef __ASSEMBLY__ + + +#include "hal/types.h" +#include + +#include "hal/gaisler/ambapp.h" +#include "hal/sparcv8leon/gaisler/gaisler.h" + +#include "include/arch/sparcv8leon/gr740/gr740.h" + + +int gaisler_setIomuxCfg(u8 pin, u8 opt, u8 pullup, u8 pulldn); + + +void _gr740_cguClkEnable(u32 device); + + +void _gr740_cguClkDisable(u32 device); + + +int _gr740_cguClkStatus(u32 device); + + +int hal_platformctl(void *ptr); + + +void _hal_platformInit(void); + + +#endif /* __ASSEMBLY__ */ + + +#endif diff --git a/hal/sparcv8leon/gaisler/irqamp.c b/hal/sparcv8leon/gaisler/irqamp.c index 00865136e..990d7836f 100644 --- a/hal/sparcv8leon/gaisler/irqamp.c +++ b/hal/sparcv8leon/gaisler/irqamp.c @@ -5,7 +5,7 @@ * * Interrupt handling - IRQAMP controller * - * Copyright 2022 Phoenix Systems + * Copyright 2022, 2024 Phoenix Systems * Author: Lukasz Leczkowski * * This file is part of Phoenix-RTOS. @@ -26,51 +26,59 @@ #define SIZE_INTERRUPTS 32 +/* clang-format off */ -/* Interrupt controller */ +/* Interrupt controller + * NOTE: Some registers may not be available depending on the configuration + */ enum { - int_level = 0, /* Interrupt level register : 0x00 */ - int_pend, /* Interrupt pending register : 0x04 */ - int_force, /* Interrupt force register : 0x08 */ - int_clear, /* Interrupt clear register : 0x0C */ - int_mpstat, /* Status register : 0x10 */ - /* Reserved : 0x14 */ - errstat = 6, /* Error mode status register : 0x18 */ - wdogctrl, /* Watchdog control register : 0x1C */ - /* Reserved : 0x20 - 0x30 */ - eint_clear = 13, /* Extended interrupt clear register : 0x34 */ - /* Reserved : 0x38 - 0x3C */ - pi_mask = 16, /* Processor interrupt mask register : 0x40 */ - /* Reserved : 0x44 - 0x7C */ - pc_force = 32, /* Processor interrupt force register : 0x80 */ - /* Reserved : 0x84 - 0xBC */ - pextack = 48, /* Extended interrupt acknowledge register : 0xC0 */ - /* Reserved : 0xC4 - 0xFC */ - tcnt0 = 64, /* Interrupt timestamp 0 counter register : 0x100 */ - istmpc0, /* Timestamp 0 control register : 0x104 */ - itstmpas0, /* Interrupt assertion timestamp 0 register : 0x108 */ + int_level = 0, /* Interrupt level register : 0x000 */ + int_pend, /* Interrupt pending register : 0x004 */ + int_force, /* Interrupt force register (CPU 0) : 0x008 */ + int_clear, /* Interrupt clear register : 0x00C */ + int_mpstat, /* Status register : 0x010 */ + broadcast, /* Broadcast register : 0x014 */ + errstat, /* Error mode status register : 0x018 */ + wdogctrl, /* Watchdog control register : 0x01C */ + asmpctrl, /* Asymmetric MP control register : 0x020 */ + icselr, /* Interrupt controller select register : 0x024 */ + /* Reserved : 0x028 - 0x030 */ + eint_clear = 13, /* Extended interrupt clear register : 0x034 */ + /* Reserved : 0x038 - 0x03C */ + pi_mask = 16, /* Processor interrupt mask register (CPU 0) : 0x040 */ + /* NUM_CPUS pi_mask registers */ + pc_force = 32, /* Processor interrupt force register (CPU 0) : 0x080 */ + /* NUM_CPUS pc_force registers */ + pextack = 48, /* Extended int acknowledge register (CPU 0) : 0x0C0 */ + /* NUM_CPUS pextack registers */ + tcnt0 = 64, /* Interrupt timestamp 0 counter register : 0x100 */ + istmpc0, /* Timestamp 0 control register : 0x104 */ + itstmpas0, /* Interrupt assertion timestamp 0 register : 0x108 */ itstmpack0, /* Interrupt acknowledge timestamp 0 register : 0x10C */ - tcnt1, /* Interrupt timestamp 1 counter register : 0x110 */ - istmpc1, /* Timestamp 1 control register : 0x114 */ - itstmpas1, /* Interrupt assertion timestamp 1 register : 0x118 */ + tcnt1, /* Interrupt timestamp 1 counter register : 0x110 */ + istmpc1, /* Timestamp 1 control register : 0x114 */ + itstmpas1, /* Interrupt assertion timestamp 1 register : 0x118 */ itstmpack1, /* Interrupt acknowledge timestamp 1 register : 0x11C */ - tcnt2, /* Interrupt timestamp 2 counter register : 0x120 */ - istmpc2, /* Timestamp 2 control register : 0x124 */ - itstmpas2, /* Interrupt assertion timestamp 2 register : 0x128 */ + tcnt2, /* Interrupt timestamp 2 counter register : 0x120 */ + istmpc2, /* Timestamp 2 control register : 0x124 */ + itstmpas2, /* Interrupt assertion timestamp 2 register : 0x128 */ itstmpack2, /* Interrupt acknowledge timestamp 2 register : 0x12C */ - tcnt3, /* Interrupt timestamp 3 counter register : 0x130 */ - istmpc3, /* Timestamp 3 control register : 0x134 */ - itstmpas3, /* Interrupt assertion timestamp 3 register : 0x138 */ + tcnt3, /* Interrupt timestamp 3 counter register : 0x130 */ + istmpc3, /* Timestamp 3 control register : 0x134 */ + itstmpas3, /* Interrupt assertion timestamp 3 register : 0x138 */ itstmpack3, /* Interrupt acknowledge timestamp 3 register : 0x13C */ - /* Reserved : 0x140 - 0x1FC */ - procbootadr = 128, /* Processor boot address register : 0x200 */ - /* Reserved : 0x204 - 0x2FC */ - irqmap = 192, /* Interrupt map register : 0x300 - 15 entries*/ + /* Reserved : 0x140 - 0x1FC */ + procbootadr = 128, /* Processor boot address register (CPU 0) : 0x200 */ + /* NUM_CPUS procbootadr registers */ + irqmap = 192, /* Interrupt map register : 0x300 - impl dependent no. entries */ }; +/* clang-format on */ + static struct { volatile u32 *int_ctrl; + u32 extendedIrqn; spinlock_t spinlocks[SIZE_INTERRUPTS]; intr_handler_t *handlers[SIZE_INTERRUPTS]; unsigned int counters[SIZE_INTERRUPTS]; @@ -82,23 +90,37 @@ extern int threads_schedule(unsigned int n, cpu_context_t *context, void *arg); void hal_cpuBroadcastIPI(unsigned int intr) { + unsigned int id = hal_cpuGetID(), i; + + for (i = 0; i < hal_cpuGetCount(); ++i) { + if (i != id) { + *(interrupts_common.int_ctrl + pc_force + i) |= (1 << intr); + } + } } void hal_cpuStartCores(void) { + unsigned int id = hal_cpuGetID(); + u32 msk = 0; + + if (id == 0) { + msk = ((1 << hal_cpuGetCount()) - 1) & ~(1 << id); + *(interrupts_common.int_ctrl + int_mpstat) = msk; + } } void interrupts_dispatch(unsigned int n, cpu_context_t *ctx) { intr_handler_t *h; - int reschedule = 0; + int reschedule = 0, cpuid = hal_cpuGetID(); spinlock_ctx_t sc; - if (n == EXTENDED_IRQN) { + if (n == interrupts_common.extendedIrqn) { /* Extended interrupt (16 - 31) */ - n = *(interrupts_common.int_ctrl + pextack) & 0x3F; + n = *(interrupts_common.int_ctrl + pextack + cpuid) & 0x3F; } if (n >= SIZE_INTERRUPTS) { @@ -122,20 +144,35 @@ void interrupts_dispatch(unsigned int n, cpu_context_t *ctx) if (reschedule != 0) { threads_schedule(n, ctx, NULL); } - hal_spinlockClear(&interrupts_common.spinlocks[n], &sc); } static void interrupts_enableIRQ(unsigned int irqn) { - *(interrupts_common.int_ctrl + pi_mask) |= (1 << irqn); + int i; + + /* TLB and Wakeup Timer IRQ should fire on all cores */ + if ((irqn == TLB_IRQ) || (irqn == TIMER0_2_IRQ)) { + for (i = 0; i < hal_cpuGetCount(); ++i) { + *(interrupts_common.int_ctrl + pi_mask + i) |= (1 << irqn); + } + *(interrupts_common.int_ctrl + broadcast) |= (1 << irqn); + } + else { + /* Other IRQs only on core 0 - no easy way to manage them */ + *(interrupts_common.int_ctrl + pi_mask) |= (1 << irqn); + } } static void interrupts_disableIRQ(unsigned int irqn) { - *(interrupts_common.int_ctrl + pi_mask) &= ~(1 << irqn); + int i; + + for (i = 0; i < hal_cpuGetCount(); i++) { + *(interrupts_common.int_ctrl + pi_mask + i) &= ~(1 << irqn); + } } @@ -188,6 +225,7 @@ char *hal_interruptsFeatures(char *features, unsigned int len) return features; } + void _hal_interruptsInit(void) { int i; @@ -199,4 +237,7 @@ void _hal_interruptsInit(void) } interrupts_common.int_ctrl = _pmap_halMapDevice(PAGE_ALIGN(INT_CTRL_BASE), PAGE_OFFS(INT_CTRL_BASE), SIZE_PAGE); + + /* Read extended irqn */ + interrupts_common.extendedIrqn = (*(interrupts_common.int_ctrl + int_mpstat) >> 16) & 0xf; } diff --git a/hal/sparcv8leon/gaisler/l2cache.c b/hal/sparcv8leon/gaisler/l2cache.c new file mode 100644 index 000000000..07fab2af9 --- /dev/null +++ b/hal/sparcv8leon/gaisler/l2cache.c @@ -0,0 +1,119 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * L2 cache management + * + * Copyright 2025 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include "l2cache.h" + +#include "hal/sparcv8leon/sparcv8leon.h" + +#include +#include + +/* clang-format off */ +enum { l2c_ctrl = 0, l2c_status, l2c_fma, l2c_fsi, l2c_err = 8, l2c_erra, l2c_tcb, l2c_dcb, l2c_scrub, + l2c_sdel, l2c_einj, l2c_accc }; +/* clang-format on */ + + +static struct { + volatile u32 *base; + size_t lineSz; /* bytes */ +} l2c_common; + + +void l2c_flushRange(unsigned int mode, ptr_t start, size_t size) +{ + u32 val; + ptr_t fstart; + ptr_t fend; + volatile u32 *freg = l2c_common.base + l2c_fma; + mode &= 0x7; + + /* TN-0021: flush register accesses must be done using atomic operations */ + + if ((mode >= l2c_inv_all) && (mode <= l2c_flush_inv_all)) { + /* clang-format off */ + __asm__ volatile ("swap [%1], %0" : "+r"(mode) : "r"(freg) : "memory"); + /* clang-format on */ + } + else { + fstart = start & ~(l2c_common.lineSz - 1); + fend = fstart + ((((start & (l2c_common.lineSz - 1)) + size) + (l2c_common.lineSz - 1)) & ~(l2c_common.lineSz - 1)); + + while (fstart < fend) { + val = fstart | mode; + + /* Flushing takes 5 cycles/line */ + + /* clang-format off */ + __asm__ volatile ( + "swap [%1], %0\n\t" + : "+r"(val) + : "r"(freg) + : "memory" + ); + /* clang-format on */ + + fstart += l2c_common.lineSz; + } + } +} + + +void l2c_init(addr_t base) +{ + u32 reg; + size_t lineSz; + + l2c_common.base = _pmap_halMapDevice(PAGE_ALIGN(base), PAGE_OFFS(base), SIZE_PAGE); + + reg = *(l2c_common.base + l2c_status); + lineSz = ((reg & (1 << 24)) != 0) ? 64 : 32; + + l2c_common.lineSz = lineSz; + + l2c_flushRange(l2c_inv_all, 0, 0); + + /* Wait for flush to complete: + * Full L2 cache invalidation takes 5 cycles for the 1st line + * and 1 cycle for each subsequent line. + * There are 0x8000 lines. + */ + + /* clang-format off */ + __asm__ volatile ( + "set 0x2001, %%g1\n\t" + "1:\n\t" + "nop\n\t" + "subcc %%g1, 1, %%g1\n\t" + "bne 1b\n\t" + "nop\n\t" + ::: "g1", "cc" + ); + /* clang-format on */ + + /* Initialize cache according to GRLIB-TN-0021 errata */ + *(l2c_common.base + l2c_err) = 0; + *(l2c_common.base + l2c_accc) = ((1 << 14) | (1 << 13) | (1 << 10) | (1 << 4) | (1 << 2) | (1 << 1)); + + /* Enable cache with default params, EDAC disabled, LRU */ + *(l2c_common.base + l2c_ctrl) = (1 << 31); + + hal_cpuDataStoreBarrier(); + + /* Perform load from cacheable memory (errata) */ + /* clang-format off */ + __asm__ volatile ("ld [%0], %%g0" :: "r"(VADDR_KERNEL): "memory"); + /* clang-format on */ +} diff --git a/hal/sparcv8leon/gaisler/l2cache.h b/hal/sparcv8leon/gaisler/l2cache.h new file mode 100644 index 000000000..e769764ce --- /dev/null +++ b/hal/sparcv8leon/gaisler/l2cache.h @@ -0,0 +1,34 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * L2 cache management + * + * Copyright 2025 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef _L2CACHE_H_ +#define _L2CACHE_H_ + + +#include + + +/* clang-format off */ +enum { l2c_inv_line = 1, l2c_flush_line, l2c_flush_inv_line, l2c_inv_all = 5, l2c_flush_all, l2c_flush_inv_all }; +/* clang-format on */ + + +void l2c_flushRange(unsigned int mode, addr_t start, size_t size); + + +void l2c_init(addr_t base); + + +#endif diff --git a/hal/sparcv8leon/gaisler/timer.c b/hal/sparcv8leon/gaisler/timer.c index 9c0ae5493..0e557d3bb 100644 --- a/hal/sparcv8leon/gaisler/timer.c +++ b/hal/sparcv8leon/gaisler/timer.c @@ -48,6 +48,7 @@ static struct { volatile u32 *timer0_base; + u32 wdog; intr_handler_t timebaseHandler; intr_handler_t wakeupHandler; volatile time_t jiffies; @@ -85,6 +86,11 @@ static int _timer_irqHandler(unsigned int irq, cpu_context_t *ctx, void *data) ret = 1; } timer_clearIrq(timer); + +#ifdef __CPU_GR740 + /* Kick watchdog (on GR740 there's a fixed PLL watchdog, restarted on watchdog timer tctrl write) */ + *(timer_common.timer0_base + GPT_TCTRL(timer_common.wdog)) |= TIMER_LOAD; +#endif } hal_spinlockClear(&timer_common.sp, &sc); @@ -142,6 +148,20 @@ void hal_timerSetWakeup(u32 waitUs) } +void hal_timerWdogReboot(void) +{ + /* Reboot system using watchdog */ + *(timer_common.timer0_base + GPT_SRELOAD) = 0; + *(timer_common.timer0_base + GPT_SCALER) = 0; + hal_cpuDataStoreBarrier(); + *(timer_common.timer0_base + GPT_TRLDVAL(timer_common.wdog)) = 1; + hal_cpuDataStoreBarrier(); + + /* Interrupt must be enabled for the watchdog to work */ + *(timer_common.timer0_base + GPT_TCTRL(timer_common.wdog)) = TIMER_LOAD | TIMER_INT_ENABLE | TIMER_ENABLE; +} + + int hal_timerRegister(int (*f)(unsigned int, cpu_context_t *, void *), void *data, intr_handler_t *h) { h->f = f; @@ -167,6 +187,7 @@ void _hal_timerInit(u32 interval) timer_common.jiffies = 0; timer_common.timer0_base = _pmap_halMapDevice(PAGE_ALIGN(GPTIMER0_BASE), PAGE_OFFS(GPTIMER0_BASE), SIZE_PAGE); + timer_common.wdog = *(timer_common.timer0_base + GPT_CONFIG) & 0x7; /* Disable timer interrupts - bits cleared when written 1 */ st = *(timer_common.timer0_base + GPT_TCTRL(TIMER_TIMEBASE)) & (TIMER_INT_ENABLE | TIMER_INT_PENDING); diff --git a/hal/sparcv8leon/pmap.c b/hal/sparcv8leon/pmap.c index e7e70810b..6d2291694 100644 --- a/hal/sparcv8leon/pmap.c +++ b/hal/sparcv8leon/pmap.c @@ -22,6 +22,7 @@ #include "hal/spinlock.h" #include "hal/sparcv8leon/sparcv8leon.h" #include "hal/tlb/tlb.h" +#include "gaisler/l2cache.h" #include "include/errno.h" #include "include/mman.h" @@ -226,7 +227,7 @@ addr_t pmap_destroy(pmap_t *pmap, int *i) pdir3 = PTD_TO_ADDR(hal_cpuLoadPaddr(&((u32 *)pdir2)[j])); if (pdir3 != NULL) { hal_cpuStorePaddr(&((u32 *)pdir2)[j], 0); - hal_cpuflushDCache(); + hal_cpuflushDCacheL1(); return pdir3; } @@ -278,13 +279,20 @@ addr_t pmap_resolve(pmap_t *pmap, void *vaddr) return addr; } - void pmap_switch(pmap_t *pmap) { spinlock_ctx_t sc; addr_t paddr; hal_spinlockSet(&pmap_common.lock, &sc); + if (hal_srmmuGetContext() == CONTEXT_SHARED) { + hal_cpuflushICacheL1(); + hal_cpuflushDCacheL1(); +#ifdef LEON_HAS_L2CACHE + l2c_flushRange(l2c_flush_inv_all, 0, 0); +#endif + } + if ((pmap->context == CONTEXT_INVALID) || ((pmap->context == CONTEXT_SHARED) && (pmap_common.numCtxFree != 0))) { pmap->context = _pmap_contextAlloc(); paddr = PTD(_pmap_resolve(pmap, pmap->pdir1) + ((u32)pmap->pdir1 & 0xfff)); @@ -292,8 +300,6 @@ void pmap_switch(pmap_t *pmap) } hal_srmmuSetContext(pmap->context); - hal_cpuflushICache(); - hal_cpuflushDCache(); if (pmap->context == CONTEXT_SHARED) { paddr = PTD(_pmap_resolve(pmap, pmap->pdir1) + ((u32)pmap->pdir1 & 0xfff)); @@ -322,7 +328,7 @@ static int _pmap_map(u32 *pdir1, addr_t pa, void *vaddr, int attr, page_t *alloc for (size_t i = 0; i < (SIZE_PAGE / sizeof(u32)); i++) { hal_cpuStorePaddr((u32 *)alloc->addr + i, 0); } - hal_cpuflushDCache(); + hal_cpuflushDCacheL1(); pdir1[idx1] = PTD(alloc->addr); @@ -348,17 +354,28 @@ static int _pmap_map(u32 *pdir1, addr_t pa, void *vaddr, int attr, page_t *alloc } hal_cpuStorePaddr(&((u32 *)pdir2)[idx2], PTD(alloc->addr)); - hal_cpuflushDCache(); + hal_cpuflushDCacheL1(); addr = PTD_TO_ADDR(hal_cpuLoadPaddr(&((u32 *)pdir2)[idx2])); alloc = NULL; } +#ifdef LEON_HAS_L2CACHE + if ((attr & (PGHD_NOT_CACHED | PGHD_DEV)) == 0) { + l2c_flushRange(l2c_flush_inv_line, (ptr_t)vaddr, SIZE_PAGE); + } +#endif + entry = PTE(pa, ((attr & (PGHD_NOT_CACHED | PGHD_DEV)) != 0) ? UNCACHED : CACHED, acc, ((attr & PGHD_PRESENT) != 0) ? PAGE_ENTRY : 0); hal_cpuStorePaddr(&((u32 *)addr)[idx3], entry); - hal_cpuflushDCache(); + hal_cpuflushDCacheL1(); + + if ((attr & PGHD_EXEC) != 0) { + hal_cpuflushICacheL1(); + } + return EOK; } @@ -423,9 +440,13 @@ int pmap_remove(pmap_t *pmap, void *vaddr) return EOK; } + hal_cpuflushDCacheL1(); +#ifdef LEON_HAS_L2CACHE + l2c_flushRange(l2c_flush_inv_line, (ptr_t)vaddr, SIZE_PAGE); +#endif addr = PTD_TO_ADDR(descr); hal_cpuStorePaddr(&((u32 *)addr)[idx3], 0); - hal_cpuflushDCache(); + hal_cpuflushDCacheL1(); hal_tlbInvalidateEntry(pmap, vaddr, 1); diff --git a/hal/sparcv8leon/sparcv8leon.h b/hal/sparcv8leon/sparcv8leon.h index 940bdf7cc..639fcf8a1 100644 --- a/hal/sparcv8leon/sparcv8leon.h +++ b/hal/sparcv8leon/sparcv8leon.h @@ -62,13 +62,13 @@ static inline void hal_cpuStoreAlternate(addr_t addr, const u32 asi, u32 val) } -static inline void hal_cpuflushDCache(void) +static inline void hal_cpuflushDCacheL1(void) { hal_cpuStoreAlternate(0, ASI_FLUSH_DCACHE, 0); } -static inline void hal_cpuflushICache(void) +static inline void hal_cpuflushICacheL1(void) { u32 ccr = hal_cpuLoadAlternate(0, ASI_CACHE_CTRL); ccr |= CCR_FI; diff --git a/include/arch/sparcv8leon/gr740/gr740.h b/include/arch/sparcv8leon/gr740/gr740.h new file mode 100644 index 000000000..63e25f123 --- /dev/null +++ b/include/arch/sparcv8leon/gr740/gr740.h @@ -0,0 +1,66 @@ +/* + * Phoenix-RTOS + * + * Operating system kernel + * + * GR740 basic peripherals control functions + * + * Copyright 2024 Phoenix Systems + * Author: Lukasz Leczkowski + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef _PHOENIX_ARCH_GR740_H_ +#define _PHOENIX_ARCH_GR740_H_ + +#define PCTL_REBOOT_MAGIC 0xaa55aa55UL + +/* clang-format off */ + +/* Clock gating unit devices */ +enum { cgudev_greth0 = 0, cgudev_greth1, cgudev_spwrouter, cgudev_pci, cgudev_milStd1553, cgudev_can, cgudev_leon4stat, + cgudev_apbuart0, cgudev_apbuart1, cgudev_spi, cgudev_promctrl }; + + +/* Pin mux config */ +enum { iomux_gpio = 0, iomux_alternateio, iomux_promio }; + + +typedef struct { + enum { pctl_set = 0, pctl_get } action; + enum { pctl_iomux = 0, pctl_cguctrl, pctl_ambapp, pctl_reboot } type; + + union { + struct { + unsigned char pin; + unsigned char opt; + unsigned char pullup; + unsigned char pulldn; + } iocfg; + + struct { + union { + enum { disable = 0, enable } state; + int stateVal; + } v; + unsigned int cgudev; + } cguctrl; + + struct { + struct _ambapp_dev_t *dev; + unsigned int *instance; + } ambapp; + + struct { + unsigned int magic; + } reboot; + } task; +} __attribute__((packed)) platformctl_t; + +/* clang-format on */ + + +#endif diff --git a/include/arch/sparcv8leon/sparcv8leon.h b/include/arch/sparcv8leon/sparcv8leon.h index 77277e970..54520eea5 100644 --- a/include/arch/sparcv8leon/sparcv8leon.h +++ b/include/arch/sparcv8leon/sparcv8leon.h @@ -23,6 +23,8 @@ #include "gr712rc/gr712rc.h" #elif defined(__CPU_GENERIC) #include "generic/generic.h" +#elif defined(__CPU_GR740) +#include "gr740/gr740.h" #else #error "Unsupported TARGET" #endif