tcpip.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /**
  2. * @file
  3. * Sequential API Main thread module
  4. *
  5. */
  6. /*
  7. * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
  8. * All rights reserved.
  9. *
  10. * Redistribution and use in source and binary forms, with or without modification,
  11. * are permitted provided that the following conditions are met:
  12. *
  13. * 1. Redistributions of source code must retain the above copyright notice,
  14. * this list of conditions and the following disclaimer.
  15. * 2. Redistributions in binary form must reproduce the above copyright notice,
  16. * this list of conditions and the following disclaimer in the documentation
  17. * and/or other materials provided with the distribution.
  18. * 3. The name of the author may not be used to endorse or promote products
  19. * derived from this software without specific prior written permission.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
  22. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  23. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
  24. * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
  26. * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
  29. * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
  30. * OF SUCH DAMAGE.
  31. *
  32. * This file is part of the lwIP TCP/IP stack.
  33. *
  34. * Author: Adam Dunkels <adam@sics.se>
  35. *
  36. */
  37. #include "lwip/opt.h"
  38. #if !NO_SYS /* don't build if not configured for use in lwipopts.h */
  39. #include "lwip/priv/tcpip_priv.h"
  40. #include "lwip/sys.h"
  41. #include "lwip/memp.h"
  42. #include "lwip/mem.h"
  43. #include "lwip/init.h"
  44. #include "lwip/ip.h"
  45. #include "lwip/pbuf.h"
  46. #include "lwip/etharp.h"
  47. #include "netif/ethernet.h"
  48. #define TCPIP_MSG_VAR_REF(name) API_VAR_REF(name)
  49. #define TCPIP_MSG_VAR_DECLARE(name) API_VAR_DECLARE(struct tcpip_msg, name)
  50. #define TCPIP_MSG_VAR_ALLOC(name) API_VAR_ALLOC(struct tcpip_msg, MEMP_TCPIP_MSG_API, name, ERR_MEM)
  51. #define TCPIP_MSG_VAR_FREE(name) API_VAR_FREE(MEMP_TCPIP_MSG_API, name)
  52. /* global variables */
  53. static tcpip_init_done_fn tcpip_init_done;
  54. static void *tcpip_init_done_arg;
  55. static sys_mbox_t mbox;
  56. #if LWIP_TCPIP_CORE_LOCKING
  57. /** The global semaphore to lock the stack. */
  58. sys_mutex_t lock_tcpip_core;
  59. #endif /* LWIP_TCPIP_CORE_LOCKING */
  60. #if LWIP_TIMERS
  61. /* wait for a message, timeouts are processed while waiting */
  62. #define TCPIP_MBOX_FETCH(mbox, msg) sys_timeouts_mbox_fetch(mbox, msg)
  63. #else /* LWIP_TIMERS */
  64. /* wait for a message with timers disabled (e.g. pass a timer-check trigger into tcpip_thread) */
  65. #define TCPIP_MBOX_FETCH(mbox, msg) sys_mbox_fetch(mbox, msg)
  66. #endif /* LWIP_TIMERS */
  67. /**
  68. * The main lwIP thread. This thread has exclusive access to lwIP core functions
  69. * (unless access to them is not locked). Other threads communicate with this
  70. * thread using message boxes.
  71. *
  72. * It also starts all the timers to make sure they are running in the right
  73. * thread context.
  74. *
  75. * @param arg unused argument
  76. */
  77. static void
  78. tcpip_thread(void *arg)
  79. {
  80. struct tcpip_msg *msg;
  81. LWIP_UNUSED_ARG(arg);
  82. if (tcpip_init_done != NULL) {
  83. tcpip_init_done(tcpip_init_done_arg);
  84. }
  85. LOCK_TCPIP_CORE();
  86. while (1) { /* MAIN Loop */
  87. UNLOCK_TCPIP_CORE();
  88. LWIP_TCPIP_THREAD_ALIVE();
  89. /* wait for a message, timeouts are processed while waiting */
  90. TCPIP_MBOX_FETCH(&mbox, (void **)&msg);
  91. LOCK_TCPIP_CORE();
  92. if (msg == NULL) {
  93. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
  94. LWIP_ASSERT("tcpip_thread: invalid message", 0);
  95. continue;
  96. }
  97. switch (msg->type) {
  98. #if !LWIP_TCPIP_CORE_LOCKING
  99. case TCPIP_MSG_API:
  100. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
  101. msg->msg.api_msg.function(msg->msg.api_msg.msg);
  102. break;
  103. case TCPIP_MSG_API_CALL:
  104. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API CALL message %p\n", (void *)msg));
  105. msg->msg.api_call.arg->err = msg->msg.api_call.function(msg->msg.api_call.arg);
  106. sys_sem_signal(msg->msg.api_call.sem);
  107. break;
  108. #endif /* !LWIP_TCPIP_CORE_LOCKING */
  109. #if !LWIP_TCPIP_CORE_LOCKING_INPUT
  110. case TCPIP_MSG_INPKT:
  111. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
  112. msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif);
  113. memp_free(MEMP_TCPIP_MSG_INPKT, msg);
  114. break;
  115. #endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
  116. #if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
  117. case TCPIP_MSG_TIMEOUT:
  118. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
  119. sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
  120. memp_free(MEMP_TCPIP_MSG_API, msg);
  121. break;
  122. case TCPIP_MSG_UNTIMEOUT:
  123. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
  124. sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
  125. memp_free(MEMP_TCPIP_MSG_API, msg);
  126. break;
  127. #endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  128. case TCPIP_MSG_CALLBACK:
  129. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
  130. msg->msg.cb.function(msg->msg.cb.ctx);
  131. memp_free(MEMP_TCPIP_MSG_API, msg);
  132. break;
  133. case TCPIP_MSG_CALLBACK_STATIC:
  134. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
  135. msg->msg.cb.function(msg->msg.cb.ctx);
  136. break;
  137. default:
  138. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
  139. LWIP_ASSERT("tcpip_thread: invalid message", 0);
  140. break;
  141. }
  142. }
  143. }
  144. /**
  145. * Pass a received packet to tcpip_thread for input processing
  146. *
  147. * @param p the received packet
  148. * @param inp the network interface on which the packet was received
  149. * @param input_fn input function to call
  150. */
  151. err_t
  152. tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
  153. {
  154. #if LWIP_TCPIP_CORE_LOCKING_INPUT
  155. err_t ret;
  156. LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_inpkt: PACKET %p/%p\n", (void *)p, (void *)inp));
  157. LOCK_TCPIP_CORE();
  158. ret = input_fn(p, inp);
  159. UNLOCK_TCPIP_CORE();
  160. return ret;
  161. #else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
  162. struct tcpip_msg *msg;
  163. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  164. msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
  165. if (msg == NULL) {
  166. return ERR_MEM;
  167. }
  168. msg->type = TCPIP_MSG_INPKT;
  169. msg->msg.inp.p = p;
  170. msg->msg.inp.netif = inp;
  171. msg->msg.inp.input_fn = input_fn;
  172. if (sys_mbox_trypost(&mbox, msg) != ERR_OK) {
  173. memp_free(MEMP_TCPIP_MSG_INPKT, msg);
  174. return ERR_MEM;
  175. }
  176. return ERR_OK;
  177. #endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
  178. }
  179. /**
  180. * @ingroup lwip_os
  181. * Pass a received packet to tcpip_thread for input processing with
  182. * ethernet_input or ip_input. Don't call directly, pass to netif_add()
  183. * and call netif->input().
  184. *
  185. * @param p the received packet, p->payload pointing to the Ethernet header or
  186. * to an IP header (if inp doesn't have NETIF_FLAG_ETHARP or
  187. * NETIF_FLAG_ETHERNET flags)
  188. * @param inp the network interface on which the packet was received
  189. */
  190. err_t
  191. tcpip_input(struct pbuf *p, struct netif *inp)
  192. {
  193. #if LWIP_ETHERNET
  194. if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
  195. return tcpip_inpkt(p, inp, ethernet_input);
  196. } else
  197. #endif /* LWIP_ETHERNET */
  198. return tcpip_inpkt(p, inp, ip_input);
  199. }
  200. /**
  201. * Call a specific function in the thread context of
  202. * tcpip_thread for easy access synchronization.
  203. * A function called in that way may access lwIP core code
  204. * without fearing concurrent access.
  205. *
  206. * @param function the function to call
  207. * @param ctx parameter passed to f
  208. * @param block 1 to block until the request is posted, 0 to non-blocking mode
  209. * @return ERR_OK if the function was called, another err_t if not
  210. */
  211. err_t
  212. tcpip_callback_with_block(tcpip_callback_fn function, void *ctx, u8_t block)
  213. {
  214. struct tcpip_msg *msg;
  215. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  216. msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
  217. if (msg == NULL) {
  218. return ERR_MEM;
  219. }
  220. msg->type = TCPIP_MSG_CALLBACK;
  221. msg->msg.cb.function = function;
  222. msg->msg.cb.ctx = ctx;
  223. if (block) {
  224. sys_mbox_post(&mbox, msg);
  225. } else {
  226. if (sys_mbox_trypost(&mbox, msg) != ERR_OK) {
  227. memp_free(MEMP_TCPIP_MSG_API, msg);
  228. return ERR_MEM;
  229. }
  230. }
  231. return ERR_OK;
  232. }
  233. #if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
  234. /**
  235. * call sys_timeout in tcpip_thread
  236. *
  237. * @param msecs time in milliseconds for timeout
  238. * @param h function to be called on timeout
  239. * @param arg argument to pass to timeout function h
  240. * @return ERR_MEM on memory error, ERR_OK otherwise
  241. */
  242. err_t
  243. tcpip_timeout(u32_t msecs, sys_timeout_handler h, void *arg)
  244. {
  245. struct tcpip_msg *msg;
  246. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  247. msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
  248. if (msg == NULL) {
  249. return ERR_MEM;
  250. }
  251. msg->type = TCPIP_MSG_TIMEOUT;
  252. msg->msg.tmo.msecs = msecs;
  253. msg->msg.tmo.h = h;
  254. msg->msg.tmo.arg = arg;
  255. sys_mbox_post(&mbox, msg);
  256. return ERR_OK;
  257. }
  258. /**
  259. * call sys_untimeout in tcpip_thread
  260. *
  261. * @param h function to be called on timeout
  262. * @param arg argument to pass to timeout function h
  263. * @return ERR_MEM on memory error, ERR_OK otherwise
  264. */
  265. err_t
  266. tcpip_untimeout(sys_timeout_handler h, void *arg)
  267. {
  268. struct tcpip_msg *msg;
  269. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  270. msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
  271. if (msg == NULL) {
  272. return ERR_MEM;
  273. }
  274. msg->type = TCPIP_MSG_UNTIMEOUT;
  275. msg->msg.tmo.h = h;
  276. msg->msg.tmo.arg = arg;
  277. sys_mbox_post(&mbox, msg);
  278. return ERR_OK;
  279. }
  280. #endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
  281. /**
  282. * Sends a message to TCPIP thread to call a function. Caller thread blocks on
  283. * on a provided semaphore, which ist NOT automatically signalled by TCPIP thread,
  284. * this has to be done by the user.
  285. * It is recommended to use LWIP_TCPIP_CORE_LOCKING since this is the way
  286. * with least runtime overhead.
  287. *
  288. * @param fn function to be called from TCPIP thread
  289. * @param apimsg argument to API function
  290. * @param sem semaphore to wait on
  291. * @return ERR_OK if the function was called, another err_t if not
  292. */
  293. err_t
  294. tcpip_send_msg_wait_sem(tcpip_callback_fn fn, void *apimsg, sys_sem_t* sem)
  295. {
  296. #if LWIP_TCPIP_CORE_LOCKING
  297. LWIP_UNUSED_ARG(sem);
  298. LOCK_TCPIP_CORE();
  299. fn(apimsg);
  300. UNLOCK_TCPIP_CORE();
  301. return ERR_OK;
  302. #else /* LWIP_TCPIP_CORE_LOCKING */
  303. TCPIP_MSG_VAR_DECLARE(msg);
  304. LWIP_ASSERT("semaphore not initialized", sys_sem_valid(sem));
  305. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  306. TCPIP_MSG_VAR_ALLOC(msg);
  307. TCPIP_MSG_VAR_REF(msg).type = TCPIP_MSG_API;
  308. TCPIP_MSG_VAR_REF(msg).msg.api_msg.function = fn;
  309. TCPIP_MSG_VAR_REF(msg).msg.api_msg.msg = apimsg;
  310. sys_mbox_post(&mbox, &TCPIP_MSG_VAR_REF(msg));
  311. sys_arch_sem_wait(sem, 0);
  312. TCPIP_MSG_VAR_FREE(msg);
  313. return ERR_OK;
  314. #endif /* LWIP_TCPIP_CORE_LOCKING */
  315. }
  316. /**
  317. * Synchronously calls function in TCPIP thread and waits for its completion.
  318. * It is recommended to use LWIP_TCPIP_CORE_LOCKING (preferred) or
  319. * LWIP_NETCONN_SEM_PER_THREAD.
  320. * If not, a semaphore is created and destroyed on every call which is usually
  321. * an expensive/slow operation.
  322. * @param fn Function to call
  323. * @param call Call parameters
  324. * @return Return value from tcpip_api_call_fn
  325. */
  326. err_t
  327. tcpip_api_call(tcpip_api_call_fn fn, struct tcpip_api_call_data *call)
  328. {
  329. #if LWIP_TCPIP_CORE_LOCKING
  330. err_t err;
  331. LOCK_TCPIP_CORE();
  332. err = fn(call);
  333. UNLOCK_TCPIP_CORE();
  334. return err;
  335. #else /* LWIP_TCPIP_CORE_LOCKING */
  336. TCPIP_MSG_VAR_DECLARE(msg);
  337. #if !LWIP_NETCONN_SEM_PER_THREAD
  338. err_t err = sys_sem_new(&call->sem, 0);
  339. if (err != ERR_OK) {
  340. return err;
  341. }
  342. #endif /* LWIP_NETCONN_SEM_PER_THREAD */
  343. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  344. TCPIP_MSG_VAR_ALLOC(msg);
  345. TCPIP_MSG_VAR_REF(msg).type = TCPIP_MSG_API_CALL;
  346. TCPIP_MSG_VAR_REF(msg).msg.api_call.arg = call;
  347. TCPIP_MSG_VAR_REF(msg).msg.api_call.function = fn;
  348. #if LWIP_NETCONN_SEM_PER_THREAD
  349. TCPIP_MSG_VAR_REF(msg).msg.api_call.sem = LWIP_NETCONN_THREAD_SEM_GET();
  350. #else /* LWIP_NETCONN_SEM_PER_THREAD */
  351. TCPIP_MSG_VAR_REF(msg).msg.api_call.sem = &call->sem;
  352. #endif /* LWIP_NETCONN_SEM_PER_THREAD */
  353. sys_mbox_post(&mbox, &TCPIP_MSG_VAR_REF(msg));
  354. sys_arch_sem_wait(TCPIP_MSG_VAR_REF(msg).msg.api_call.sem, 0);
  355. TCPIP_MSG_VAR_FREE(msg);
  356. #if !LWIP_NETCONN_SEM_PER_THREAD
  357. sys_sem_free(&call->sem);
  358. #endif /* LWIP_NETCONN_SEM_PER_THREAD */
  359. return call->err;
  360. #endif /* LWIP_TCPIP_CORE_LOCKING */
  361. }
  362. /**
  363. * Allocate a structure for a static callback message and initialize it.
  364. * This is intended to be used to send "static" messages from interrupt context.
  365. *
  366. * @param function the function to call
  367. * @param ctx parameter passed to function
  368. * @return a struct pointer to pass to tcpip_trycallback().
  369. */
  370. struct tcpip_callback_msg*
  371. tcpip_callbackmsg_new(tcpip_callback_fn function, void *ctx)
  372. {
  373. struct tcpip_msg *msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
  374. if (msg == NULL) {
  375. return NULL;
  376. }
  377. msg->type = TCPIP_MSG_CALLBACK_STATIC;
  378. msg->msg.cb.function = function;
  379. msg->msg.cb.ctx = ctx;
  380. return (struct tcpip_callback_msg*)msg;
  381. }
  382. /**
  383. * Free a callback message allocated by tcpip_callbackmsg_new().
  384. *
  385. * @param msg the message to free
  386. */
  387. void
  388. tcpip_callbackmsg_delete(struct tcpip_callback_msg* msg)
  389. {
  390. memp_free(MEMP_TCPIP_MSG_API, msg);
  391. }
  392. /**
  393. * Try to post a callback-message to the tcpip_thread mbox
  394. * This is intended to be used to send "static" messages from interrupt context.
  395. *
  396. * @param msg pointer to the message to post
  397. * @return sys_mbox_trypost() return code
  398. */
  399. err_t
  400. tcpip_trycallback(struct tcpip_callback_msg* msg)
  401. {
  402. LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  403. return sys_mbox_trypost(&mbox, msg);
  404. }
  405. /**
  406. * @ingroup lwip_os
  407. * Initialize this module:
  408. * - initialize all sub modules
  409. * - start the tcpip_thread
  410. *
  411. * @param initfunc a function to call when tcpip_thread is running and finished initializing
  412. * @param arg argument to pass to initfunc
  413. */
  414. void
  415. tcpip_init(tcpip_init_done_fn initfunc, void *arg)
  416. {
  417. lwip_init();
  418. tcpip_init_done = initfunc;
  419. tcpip_init_done_arg = arg;
  420. if (sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
  421. LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
  422. }
  423. #if LWIP_TCPIP_CORE_LOCKING
  424. if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
  425. LWIP_ASSERT("failed to create lock_tcpip_core", 0);
  426. }
  427. #endif /* LWIP_TCPIP_CORE_LOCKING */
  428. sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
  429. }
  430. /**
  431. * Simple callback function used with tcpip_callback to free a pbuf
  432. * (pbuf_free has a wrong signature for tcpip_callback)
  433. *
  434. * @param p The pbuf (chain) to be dereferenced.
  435. */
  436. static void
  437. pbuf_free_int(void *p)
  438. {
  439. struct pbuf *q = (struct pbuf *)p;
  440. pbuf_free(q);
  441. }
  442. /**
  443. * A simple wrapper function that allows you to free a pbuf from interrupt context.
  444. *
  445. * @param p The pbuf (chain) to be dereferenced.
  446. * @return ERR_OK if callback could be enqueued, an err_t if not
  447. */
  448. err_t
  449. pbuf_free_callback(struct pbuf *p)
  450. {
  451. return tcpip_callback_with_block(pbuf_free_int, p, 0);
  452. }
  453. /**
  454. * A simple wrapper function that allows you to free heap memory from
  455. * interrupt context.
  456. *
  457. * @param m the heap memory to free
  458. * @return ERR_OK if callback could be enqueued, an err_t if not
  459. */
  460. err_t
  461. mem_free_callback(void *m)
  462. {
  463. return tcpip_callback_with_block(mem_free, m, 0);
  464. }
  465. #endif /* !NO_SYS */