CV_CoreFunc.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /*-----------------------------------------------------------------------------
  2. * Name: CV_CoreFunc.c
  3. * Purpose: CMSIS CORE validation tests implementation
  4. *-----------------------------------------------------------------------------
  5. * Copyright (c) 2017 ARM Limited. All rights reserved.
  6. *----------------------------------------------------------------------------*/
  7. #include "CV_Framework.h"
  8. #include "cmsis_cv.h"
  9. /*-----------------------------------------------------------------------------
  10. * Test implementation
  11. *----------------------------------------------------------------------------*/
  12. static volatile uint32_t irqTaken = 0U;
  13. #if defined(__CORTEX_M) && (__CORTEX_M > 0)
  14. static volatile uint32_t irqActive = 0U;
  15. #endif
  16. static void TC_CoreFunc_EnDisIRQIRQHandler(void) {
  17. ++irqTaken;
  18. #if defined(__CORTEX_M) && (__CORTEX_M > 0)
  19. irqActive = NVIC_GetActive(WDT_IRQn);
  20. #endif
  21. }
  22. static volatile uint32_t irqIPSR = 0U;
  23. static volatile uint32_t irqXPSR = 0U;
  24. static void TC_CoreFunc_IPSR_IRQHandler(void) {
  25. irqIPSR = __get_IPSR();
  26. irqXPSR = __get_xPSR();
  27. }
  28. /*-----------------------------------------------------------------------------
  29. * Test cases
  30. *----------------------------------------------------------------------------*/
  31. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  32. /**
  33. \brief Test case: TC_CoreFunc_EnDisIRQ
  34. \details
  35. Check expected behavior of interrupt related control functions:
  36. - __disable_irq() and __enable_irq()
  37. - NVIC_EnableIRQ, NVIC_DisableIRQ, and NVIC_GetEnableIRQ
  38. - NVIC_SetPendingIRQ, NVIC_ClearPendingIRQ, and NVIC_GetPendingIRQ
  39. - NVIC_GetActive (not on Cortex-M0/M0+)
  40. */
  41. void TC_CoreFunc_EnDisIRQ (void)
  42. {
  43. // Globally disable all interrupt servicing
  44. __disable_irq();
  45. // Enable the interrupt
  46. NVIC_EnableIRQ(WDT_IRQn);
  47. ASSERT_TRUE(NVIC_GetEnableIRQ(WDT_IRQn) != 0U);
  48. // Clear its pending state
  49. NVIC_ClearPendingIRQ(WDT_IRQn);
  50. ASSERT_TRUE(NVIC_GetPendingIRQ(WDT_IRQn) == 0U);
  51. // Register test interrupt handler.
  52. TST_IRQHandler = TC_CoreFunc_EnDisIRQIRQHandler;
  53. irqTaken = 0U;
  54. #if defined(__CORTEX_M) && (__CORTEX_M > 0)
  55. irqActive = UINT32_MAX;
  56. #endif
  57. // Set the interrupt pending state
  58. NVIC_SetPendingIRQ(WDT_IRQn);
  59. for(uint32_t i = 10U; i > 0U; --i) {}
  60. // Interrupt is not taken
  61. ASSERT_TRUE(irqTaken == 0U);
  62. ASSERT_TRUE(NVIC_GetPendingIRQ(WDT_IRQn) != 0U);
  63. #if defined(__CORTEX_M) && (__CORTEX_M > 0)
  64. ASSERT_TRUE(NVIC_GetActive(WDT_IRQn) == 0U);
  65. #endif
  66. // Globally enable interrupt servicing
  67. __enable_irq();
  68. for(uint32_t i = 10U; i > 0U; --i) {}
  69. // Interrupt was taken
  70. ASSERT_TRUE(irqTaken == 1U);
  71. #if defined(__CORTEX_M) && (__CORTEX_M > 0)
  72. ASSERT_TRUE(irqActive != 0U);
  73. ASSERT_TRUE(NVIC_GetActive(WDT_IRQn) == 0U);
  74. #endif
  75. // Interrupt it not pending anymore.
  76. ASSERT_TRUE(NVIC_GetPendingIRQ(WDT_IRQn) == 0U);
  77. // Disable interrupt
  78. NVIC_DisableIRQ(WDT_IRQn);
  79. ASSERT_TRUE(NVIC_GetEnableIRQ(WDT_IRQn) == 0U);
  80. // Set interrupt pending
  81. NVIC_SetPendingIRQ(WDT_IRQn);
  82. for(uint32_t i = 10U; i > 0U; --i) {}
  83. // Interrupt is not taken again
  84. ASSERT_TRUE(irqTaken == 1U);
  85. ASSERT_TRUE(NVIC_GetPendingIRQ(WDT_IRQn) != 0U);
  86. // Clear interrupt pending
  87. NVIC_ClearPendingIRQ(WDT_IRQn);
  88. for(uint32_t i = 10U; i > 0U; --i) {}
  89. // Interrupt it not pending anymore.
  90. ASSERT_TRUE(NVIC_GetPendingIRQ(WDT_IRQn) == 0U);
  91. // Globally disable interrupt servicing
  92. __disable_irq();
  93. }
  94. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  95. /**
  96. \brief Test case: TC_CoreFunc_GetCtrl
  97. \details
  98. - Check if __set_CONTROL and __get_CONTROL() sets/gets control register
  99. */
  100. void TC_CoreFunc_Control (void) {
  101. // don't use stack for this variables
  102. static uint32_t orig;
  103. static uint32_t ctrl;
  104. static uint32_t result;
  105. orig = __get_CONTROL();
  106. ctrl = orig;
  107. result = UINT32_MAX;
  108. #ifdef CONTROL_SPSEL_Msk
  109. // toggle SPSEL
  110. ctrl = (ctrl & ~CONTROL_SPSEL_Msk) | (~ctrl & CONTROL_SPSEL_Msk);
  111. #endif
  112. __set_CONTROL(ctrl);
  113. __ISB();
  114. result = __get_CONTROL();
  115. __set_CONTROL(orig);
  116. __ISB();
  117. ASSERT_TRUE(result == ctrl);
  118. ASSERT_TRUE(__get_CONTROL() == orig);
  119. }
  120. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  121. /**
  122. \brief Test case: TC_CoreFunc_IPSR
  123. \details
  124. - Check if __get_IPSR intrinsic is available
  125. - Check if __get_xPSR intrinsic is available
  126. - Result differentiates between thread and exception modes
  127. */
  128. void TC_CoreFunc_IPSR (void) {
  129. uint32_t result = __get_IPSR();
  130. ASSERT_TRUE(result == 0U); // Thread Mode
  131. result = __get_xPSR();
  132. ASSERT_TRUE((result & xPSR_ISR_Msk) == 0U); // Thread Mode
  133. TST_IRQHandler = TC_CoreFunc_IPSR_IRQHandler;
  134. irqIPSR = 0U;
  135. irqXPSR = 0U;
  136. NVIC_ClearPendingIRQ(WDT_IRQn);
  137. NVIC_EnableIRQ(WDT_IRQn);
  138. __enable_irq();
  139. NVIC_SetPendingIRQ(WDT_IRQn);
  140. for(uint32_t i = 10U; i > 0U; --i) {}
  141. __disable_irq();
  142. NVIC_DisableIRQ(WDT_IRQn);
  143. ASSERT_TRUE(irqIPSR != 0U); // Exception Mode
  144. ASSERT_TRUE((irqXPSR & xPSR_ISR_Msk) != 0U); // Exception Mode
  145. }
  146. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  147. #if defined(__CC_ARM)
  148. #define SUBS(Rd, Rm, Rn) __ASM("SUBS " # Rd ", " # Rm ", " # Rn)
  149. #define ADDS(Rd, Rm, Rn) __ASM("ADDS " # Rd ", " # Rm ", " # Rn)
  150. #elif defined( __GNUC__ ) && (defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_8M_BASE__))
  151. #define SUBS(Rd, Rm, Rn) __ASM("SUB %0, %1, %2" : "=r"(Rd) : "r"(Rm), "r"(Rn) : "cc")
  152. #define ADDS(Rd, Rm, Rn) __ASM("ADD %0, %1, %2" : "=r"(Rd) : "r"(Rm), "r"(Rn) : "cc")
  153. #elif defined(_lint)
  154. //lint -save -e(9026) allow function-like macro
  155. #define SUBS(Rd, Rm, Rn) ((Rd) = (Rm) - (Rn))
  156. #define ADDS(Rd, Rm, Rn) ((Rd) = (Rm) + (Rn))
  157. //lint -restore
  158. #else
  159. #define SUBS(Rd, Rm, Rn) __ASM("SUBS %0, %1, %2" : "=r"(Rd) : "r"(Rm), "r"(Rn) : "cc")
  160. #define ADDS(Rd, Rm, Rn) __ASM("ADDS %0, %1, %2" : "=r"(Rd) : "r"(Rm), "r"(Rn) : "cc")
  161. #endif
  162. /**
  163. \brief Test case: TC_CoreFunc_APSR
  164. \details
  165. - Check if __get_APSR intrinsic is available
  166. - Check if __get_xPSR intrinsic is available
  167. - Check negative, zero and overflow flags
  168. */
  169. void TC_CoreFunc_APSR (void) {
  170. uint32_t result;
  171. //lint -esym(838, Rm) unused values
  172. //lint -esym(438, Rm) unused values
  173. // Check negative flag
  174. int32_t Rm = 5;
  175. int32_t Rn = 7;
  176. SUBS(Rm, Rm, Rn);
  177. result = __get_APSR();
  178. ASSERT_TRUE((result & APSR_N_Msk) == APSR_N_Msk);
  179. Rm = 5;
  180. Rn = 7;
  181. SUBS(Rm, Rm, Rn);
  182. result = __get_xPSR();
  183. ASSERT_TRUE((result & xPSR_N_Msk) == xPSR_N_Msk);
  184. // Check zero and compare flag
  185. Rm = 5;
  186. SUBS(Rm, Rm, Rm);
  187. result = __get_APSR();
  188. ASSERT_TRUE((result & APSR_Z_Msk) == APSR_Z_Msk);
  189. ASSERT_TRUE((result & APSR_C_Msk) == APSR_C_Msk);
  190. Rm = 5;
  191. SUBS(Rm, Rm, Rm);
  192. result = __get_xPSR();
  193. ASSERT_TRUE((result & xPSR_Z_Msk) == xPSR_Z_Msk);
  194. ASSERT_TRUE((result & APSR_C_Msk) == APSR_C_Msk);
  195. // Check overflow flag
  196. Rm = 5;
  197. Rn = INT32_MAX;
  198. ADDS(Rm, Rm, Rn);
  199. result = __get_APSR();
  200. ASSERT_TRUE((result & APSR_V_Msk) == APSR_V_Msk);
  201. Rm = 5;
  202. Rn = INT32_MAX;
  203. ADDS(Rm, Rm, Rn);
  204. result = __get_xPSR();
  205. ASSERT_TRUE((result & xPSR_V_Msk) == xPSR_V_Msk);
  206. }
  207. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  208. /**
  209. \brief Test case: TC_CoreFunc_PSP
  210. \details
  211. - Check if __get_PSP and __set_PSP intrinsic can be used to manipulate process stack pointer.
  212. */
  213. void TC_CoreFunc_PSP (void) {
  214. // don't use stack for this variables
  215. static uint32_t orig;
  216. static uint32_t psp;
  217. static uint32_t result;
  218. orig = __get_PSP();
  219. psp = orig + 0x12345678U;
  220. __set_PSP(psp);
  221. result = __get_PSP();
  222. __set_PSP(orig);
  223. ASSERT_TRUE(result == psp);
  224. }
  225. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  226. /**
  227. \brief Test case: TC_CoreFunc_MSP
  228. \details
  229. - Check if __get_MSP and __set_MSP intrinsic can be used to manipulate main stack pointer.
  230. */
  231. void TC_CoreFunc_MSP (void) {
  232. // don't use stack for this variables
  233. static uint32_t orig;
  234. static uint32_t msp;
  235. static uint32_t result;
  236. static uint32_t ctrl;
  237. ctrl = __get_CONTROL();
  238. __set_CONTROL(ctrl | CONTROL_SPSEL_Msk); // switch to PSP
  239. orig = __get_MSP();
  240. msp = orig + 0x12345678U;
  241. __set_MSP(msp);
  242. result = __get_MSP();
  243. __set_MSP(orig);
  244. __set_CONTROL(ctrl);
  245. ASSERT_TRUE(result == msp);
  246. }
  247. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  248. #if ((defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) || \
  249. (defined (__ARM_ARCH_8M_BASE__ ) && (__ARM_ARCH_8M_BASE__ == 1)) )
  250. /**
  251. \brief Test case: TC_CoreFunc_PSPLIM
  252. \details
  253. - Check if __get_PSPLIM and __set_PSPLIM intrinsic can be used to manipulate process stack pointer limit.
  254. */
  255. void TC_CoreFunc_PSPLIM (void) {
  256. // don't use stack for this variables
  257. static uint32_t orig;
  258. static uint32_t psplim;
  259. static uint32_t result;
  260. orig = __get_PSPLIM();
  261. psplim = orig + 0x12345678U;
  262. __set_PSPLIM(psplim);
  263. result = __get_PSPLIM();
  264. __set_PSPLIM(orig);
  265. #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \
  266. (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3)))
  267. // without main extensions, the non-secure PSPLIM is RAZ/WI
  268. ASSERT_TRUE(result == 0U);
  269. #else
  270. ASSERT_TRUE(result == psplim);
  271. #endif
  272. }
  273. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  274. /**
  275. \brief Test case: TC_CoreFunc_PSPLIM_NS
  276. \details
  277. - Check if __TZ_get_PSPLIM_NS and __TZ_set_PSPLIM_NS intrinsic can be used to manipulate process stack pointer limit.
  278. */
  279. void TC_CoreFunc_PSPLIM_NS (void) {
  280. #if (defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3))
  281. uint32_t orig;
  282. uint32_t psplim;
  283. uint32_t result;
  284. orig = __TZ_get_PSPLIM_NS();
  285. psplim = orig + 0x12345678U;
  286. __TZ_set_PSPLIM_NS(psplim);
  287. result = __TZ_get_PSPLIM_NS();
  288. __TZ_set_PSPLIM_NS(orig);
  289. #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)))
  290. // without main extensions, the non-secure PSPLIM is RAZ/WI
  291. ASSERT_TRUE(result == 0U);
  292. #else
  293. ASSERT_TRUE(result == psplim);
  294. #endif
  295. #endif
  296. }
  297. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  298. /**
  299. \brief Test case: TC_CoreFunc_MSPLIM
  300. \details
  301. - Check if __get_MSPLIM and __set_MSPLIM intrinsic can be used to manipulate main stack pointer limit.
  302. */
  303. void TC_CoreFunc_MSPLIM (void) {
  304. // don't use stack for this variables
  305. static uint32_t orig;
  306. static uint32_t msplim;
  307. static uint32_t result;
  308. static uint32_t ctrl;
  309. ctrl = __get_CONTROL();
  310. __set_CONTROL(ctrl | CONTROL_SPSEL_Msk); // switch to PSP
  311. orig = __get_MSPLIM();
  312. msplim = orig + 0x12345678U;
  313. __set_MSPLIM(msplim);
  314. result = __get_MSPLIM();
  315. __set_MSPLIM(orig);
  316. __set_CONTROL(ctrl);
  317. #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) && \
  318. (!defined (__ARM_FEATURE_CMSE ) || (__ARM_FEATURE_CMSE < 3)))
  319. // without main extensions, the non-secure MSPLIM is RAZ/WI
  320. ASSERT_TRUE(result == 0U);
  321. #else
  322. ASSERT_TRUE(result == msplim);
  323. #endif
  324. }
  325. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  326. /**
  327. \brief Test case: TC_CoreFunc_MSPLIM_NS
  328. \details
  329. - Check if __TZ_get_MSPLIM_NS and __TZ_set_MSPLIM_NS intrinsic can be used to manipulate process stack pointer limit.
  330. */
  331. void TC_CoreFunc_MSPLIM_NS (void) {
  332. #if (defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3))
  333. uint32_t orig;
  334. uint32_t msplim;
  335. uint32_t result;
  336. orig = __TZ_get_MSPLIM_NS();
  337. msplim = orig + 0x12345678U;
  338. __TZ_set_MSPLIM_NS(msplim);
  339. result = __TZ_get_MSPLIM_NS();
  340. __TZ_set_MSPLIM_NS(orig);
  341. #if (!(defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)))
  342. // without main extensions, the non-secure MSPLIM is RAZ/WI
  343. ASSERT_TRUE(result == 0U);
  344. #else
  345. ASSERT_TRUE(result == msplim);
  346. #endif
  347. #endif
  348. }
  349. #endif
  350. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  351. /**
  352. \brief Test case: TC_CoreFunc_PRIMASK
  353. \details
  354. - Check if __get_PRIMASK and __set_PRIMASK intrinsic can be used to manipulate PRIMASK.
  355. - Check if __enable_irq and __disable_irq are reflected in PRIMASK.
  356. */
  357. void TC_CoreFunc_PRIMASK (void) {
  358. uint32_t orig = __get_PRIMASK();
  359. // toggle primask
  360. uint32_t primask = (orig & ~0x01U) | (~orig & 0x01U);
  361. __set_PRIMASK(primask);
  362. uint32_t result = __get_PRIMASK();
  363. ASSERT_TRUE(result == primask);
  364. __disable_irq();
  365. result = __get_PRIMASK();
  366. ASSERT_TRUE((result & 0x01U) == 1U);
  367. __enable_irq();
  368. result = __get_PRIMASK();
  369. ASSERT_TRUE((result & 0x01U) == 0U);
  370. __disable_irq();
  371. result = __get_PRIMASK();
  372. ASSERT_TRUE((result & 0x01U) == 1U);
  373. __set_PRIMASK(orig);
  374. }
  375. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  376. #if ((defined (__ARM_ARCH_7M__ ) && (__ARM_ARCH_7M__ == 1)) || \
  377. (defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \
  378. (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) )
  379. /**
  380. \brief Test case: TC_CoreFunc_FAULTMASK
  381. \details
  382. - Check if __get_FAULTMASK and __set_FAULTMASK intrinsic can be used to manipulate FAULTMASK.
  383. - Check if __enable_fault_irq and __disable_fault_irq are reflected in FAULTMASK.
  384. */
  385. void TC_CoreFunc_FAULTMASK (void) {
  386. uint32_t orig = __get_FAULTMASK();
  387. // toggle faultmask
  388. uint32_t faultmask = (orig & ~0x01U) | (~orig & 0x01U);
  389. __set_FAULTMASK(faultmask);
  390. uint32_t result = __get_FAULTMASK();
  391. ASSERT_TRUE(result == faultmask);
  392. __disable_fault_irq();
  393. result = __get_FAULTMASK();
  394. ASSERT_TRUE((result & 0x01U) == 1U);
  395. __enable_fault_irq();
  396. result = __get_FAULTMASK();
  397. ASSERT_TRUE((result & 0x01U) == 0U);
  398. __disable_fault_irq();
  399. result = __get_FAULTMASK();
  400. ASSERT_TRUE((result & 0x01U) == 1U);
  401. __set_FAULTMASK(orig);
  402. }
  403. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  404. /**
  405. \brief Test case: TC_CoreFunc_BASEPRI
  406. \details
  407. - Check if __get_BASEPRI and __set_BASEPRI intrinsic can be used to manipulate BASEPRI.
  408. - Check if __set_BASEPRI_MAX intrinsic can be used to manipulate BASEPRI.
  409. */
  410. void TC_CoreFunc_BASEPRI(void) {
  411. uint32_t orig = __get_BASEPRI();
  412. uint32_t basepri = ~orig & 0x80U;
  413. __set_BASEPRI(basepri);
  414. uint32_t result = __get_BASEPRI();
  415. ASSERT_TRUE(result == basepri);
  416. __set_BASEPRI(orig);
  417. __set_BASEPRI_MAX(basepri);
  418. result = __get_BASEPRI();
  419. ASSERT_TRUE(result == basepri);
  420. }
  421. #endif
  422. /*=======0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1====*/
  423. #if ((defined (__ARM_ARCH_7EM__ ) && (__ARM_ARCH_7EM__ == 1)) || \
  424. (defined (__ARM_ARCH_8M_MAIN__ ) && (__ARM_ARCH_8M_MAIN__ == 1)) )
  425. /**
  426. \brief Test case: TC_CoreFunc_BASEPRI
  427. \details
  428. - Check if __get_FPSCR and __set_FPSCR intrinsics can be used
  429. */
  430. void TC_CoreFunc_FPSCR(void) {
  431. uint32_t fpscr = __get_FPSCR();
  432. __ISB();
  433. __DSB();
  434. __set_FPSCR(~fpscr);
  435. __ISB();
  436. __DSB();
  437. uint32_t result = __get_FPSCR();
  438. __set_FPSCR(fpscr);
  439. #if (defined (__FPU_USED ) && (__FPU_USED == 1U))
  440. ASSERT_TRUE(result != fpscr);
  441. #else
  442. (void)result;
  443. #endif
  444. }
  445. #endif