swtimer.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /* swTimer.c SDK timer suspend API
  2. *
  3. * SDK software timer API info:
  4. *
  5. * The SDK software timer uses a linked list called `os_timer_t* timer_list` to keep track of
  6. * all currently armed timers.
  7. *
  8. * The SDK software timer API executes in a task. The priority of this task in relation to the
  9. * application level tasks is unknown (at time of writing).
  10. *
  11. * To determine when a timer's callback should be executed, the respective timer's `timer_expire`
  12. * variable is compared to the hardware counter(FRC2), then, if the timer's `timer_expire` is
  13. * less than the current FRC2 count, the timer's callback is fired.
  14. *
  15. * The timers in this list are organized in an ascending order starting with the timer
  16. * with the lowest timer_expire.
  17. *
  18. * When a timer expires that has a timer_period greater than 0, timer_expire is changed to current
  19. * FRC2 + timer_period, then the timer is inserted back in to the list in the correct position.
  20. *
  21. * When using millisecond(default) timers, FRC2 resolution is 312.5 ticks per millisecond.
  22. *
  23. *
  24. * TIMER SUSPEND API INFO:
  25. *
  26. * Timer suspension is achieved by first finding any non-SDK timers by comparing the timer function
  27. * callback pointer of each timer in "timer_list" to a list of registered timer callback pointers
  28. * stored in the Lua registry. If a timer with a corresponding registered callback pointer is found,
  29. * the timer's timer_expire field is is compared to the current FRC2 count and the difference is
  30. * saved along with the other timer parameters to temporary variables. The timer is then disarmed
  31. * and the parameters are copied back, the timer pointer is then added to a separate linked list of
  32. * which the head pointer is stored as a lightuserdata in the lua registry.
  33. *
  34. * Resuming the timers is achieved by first retrieving the lightuserdata holding the suspended timer
  35. * list head pointer. Then, starting with the beginning of the list the current FRC2 count is added
  36. * back to the timer's timer_expire, then the timer is manually added back to "timer_list" in an
  37. * ascending order. Once there are no more suspended timers, the function returns.
  38. *
  39. *
  40. */
  41. #include "module.h"
  42. #include "lauxlib.h"
  43. #include "platform.h"
  44. #include "task/task.h"
  45. #include "user_interface.h"
  46. #include "user_modules.h"
  47. #include <string.h>
  48. #include <stdlib.h>
  49. #include <ctype.h>
  50. #include <stdint.h>
  51. #include <stddef.h>
  52. //#define SWTMR_DEBUG
  53. #if !defined(SWTMR_DBG) && defined(LUA_USE_MODULES_SWTMR_DBG)
  54. #define SWTMR_DEBUG
  55. #endif
  56. // The SWTMR table is either normally stored in the Lua registry, but at _G.SWTMR_registry_key
  57. // when in debug. THe CB and suspend lists have different names depending of debugging mode.
  58. // Also
  59. #ifdef SWTMR_DEBUG
  60. #define SWTMR_DBG(fmt, ...) dbg_printf("\n SWTMR_DBG(%s): "fmt"\n", __FUNCTION__, ##__VA_ARGS__)
  61. #define CB_LIST_STR "timer_cb_ptrs"
  62. #define SUSP_LIST_STR "suspended_tmr_LL_head"
  63. #define get_swtmr_registry(L) lua_getglobal(L, "SWTMR_registry_key")
  64. #define set_swtmr_registry(L) lua_setglobal(L, "SWTMR_registry_key")
  65. #else
  66. #define SWTMR_DBG(...)
  67. #define CB_LIST_STR "cb"
  68. #define SUSP_LIST_STR "st"
  69. #define get_swtmr_registry(L) lua_pushlightuserdata(L, &register_queue); \
  70. lua_rawget(L, LUA_REGISTRYINDEX)
  71. #define set_swtmr_registry(L) lua_pushlightuserdata(L, &register_queue); \
  72. lua_insert(L, -2); \
  73. lua_rawset(L, LUA_REGISTRYINDEX)
  74. #endif
  75. typedef struct tmr_cb_queue{
  76. os_timer_func_t *tmr_cb_ptr;
  77. uint8 suspend_policy;
  78. struct tmr_cb_queue * next;
  79. }tmr_cb_queue_t;
  80. typedef struct cb_registry_item{
  81. os_timer_func_t *tmr_cb_ptr;
  82. uint8 suspend_policy;
  83. }cb_registry_item_t;
  84. /* Internal variables */
  85. static tmr_cb_queue_t* register_queue = NULL;
  86. static task_handle_t cb_register_task_id = 0; //variable to hold task id for task handler(process_cb_register_queue)
  87. /* Function declarations */
  88. //void swtmr_cb_register(void* timer_cb_ptr, uint8 resume_policy);
  89. static void add_to_reg_queue(void* timer_cb_ptr, uint8 suspend_policy);
  90. static void process_cb_register_queue(task_param_t param, uint8 priority);
  91. #include <pm/swtimer.h>
  92. void swtmr_suspend_timers(){
  93. lua_State* L = lua_getstate();
  94. //get swtimer table
  95. get_swtmr_registry(L);
  96. if(!lua_istable(L, -1)) {lua_pop(L, 1); return;}
  97. //get cb_list table
  98. lua_pushstring(L, CB_LIST_STR);
  99. lua_rawget(L, -2);
  100. //check for existence of the cb_list table, return if not found
  101. if(!lua_istable(L, -1)) {lua_pop(L, 2); return;}
  102. os_timer_t* suspended_timer_list_head = NULL;
  103. os_timer_t* suspended_timer_list_tail = NULL;
  104. //get suspended_timer_list table
  105. lua_pushstring(L, SUSP_LIST_STR);
  106. lua_rawget(L, -3);
  107. //if suspended_timer_list exists, find tail of list
  108. if(lua_isuserdata(L, -1)){
  109. suspended_timer_list_head = suspended_timer_list_tail = lua_touserdata(L, -1);
  110. while(suspended_timer_list_tail->timer_next != NULL){
  111. suspended_timer_list_tail = suspended_timer_list_tail->timer_next;
  112. }
  113. }
  114. lua_pop(L, 1);
  115. //get length of lua table containing the callback pointers
  116. size_t registered_cb_qty = lua_objlen(L, -1);
  117. //allocate a temporary array to hold the list of callback pointers
  118. cb_registry_item_t** cb_reg_array = calloc(1,sizeof(cb_registry_item_t*)*registered_cb_qty);
  119. if(!cb_reg_array){
  120. luaL_error(L, "%s: unable to suspend timers, out of memory!", __func__);
  121. return;
  122. }
  123. uint8 index = 0;
  124. //convert lua table cb_list to c array
  125. lua_pushnil(L);
  126. while(lua_next(L, -2) != 0){
  127. if(lua_isuserdata(L, -1)){
  128. cb_reg_array[index] = lua_touserdata(L, -1);
  129. }
  130. lua_pop(L, 1);
  131. index++;
  132. }
  133. //the cb_list table is no longer needed, pop it from the stack
  134. lua_pop(L, 1);
  135. volatile uint32 frc2_count = RTC_REG_READ(FRC2_COUNT_ADDRESS);
  136. os_timer_t* timer_ptr = timer_list;
  137. uint32 expire_temp = 0;
  138. uint32 period_temp = 0;
  139. void* arg_temp = NULL;
  140. /* In this section, the SDK's timer_list is traversed to find any timers that have a registered
  141. * callback pointer. If a registered callback is found, the timer is suspended by saving the
  142. * difference between frc2_count and timer_expire then the timer is disarmed and placed into
  143. * suspended_timer_list so it can later be resumed.
  144. */
  145. while(timer_ptr != NULL){
  146. os_timer_t* next_timer = (os_timer_t*)0xffffffff;
  147. for(size_t i = 0; i < registered_cb_qty; i++){
  148. if(timer_ptr->timer_func == cb_reg_array[i]->tmr_cb_ptr){
  149. //current timer will be suspended, next timer's pointer will be needed to continue processing timer_list
  150. next_timer = timer_ptr->timer_next;
  151. //store timer parameters temporarily so the timer can be disarmed
  152. if(timer_ptr->timer_expire < frc2_count)
  153. expire_temp = 2; // 16 us in ticks (1 tick = ~3.2 us) (arbitrarily chosen value)
  154. else
  155. expire_temp = timer_ptr->timer_expire - frc2_count;
  156. period_temp = timer_ptr->timer_period;
  157. arg_temp = timer_ptr->timer_arg;
  158. if(timer_ptr->timer_period == 0 && cb_reg_array[i]->suspend_policy == SWTIMER_RESTART){
  159. SWTMR_DBG("Warning: suspend_policy(RESTART) is not compatible with single-shot timer(%p), "
  160. "changing suspend_policy to (RESUME)", timer_ptr);
  161. cb_reg_array[i]->suspend_policy = SWTIMER_RESUME;
  162. }
  163. //remove the timer from timer_list so we don't have to.
  164. os_timer_disarm(timer_ptr);
  165. timer_ptr->timer_next = NULL;
  166. //this section determines timer behavior on resume
  167. if(cb_reg_array[i]->suspend_policy == SWTIMER_DROP){
  168. SWTMR_DBG("timer(%p) was disarmed and will not be resumed", timer_ptr);
  169. }
  170. else if(cb_reg_array[i]->suspend_policy == SWTIMER_IMMEDIATE){
  171. timer_ptr->timer_expire = 1;
  172. SWTMR_DBG("timer(%p) will fire immediately on resume", timer_ptr);
  173. }
  174. else if(cb_reg_array[i]->suspend_policy == SWTIMER_RESTART){
  175. timer_ptr->timer_expire = period_temp;
  176. SWTMR_DBG("timer(%p) will be restarted on resume", timer_ptr);
  177. }
  178. else{
  179. timer_ptr->timer_expire = expire_temp;
  180. SWTMR_DBG("timer(%p) will be resumed with remaining time", timer_ptr);
  181. }
  182. if(cb_reg_array[i]->suspend_policy != SWTIMER_DROP){
  183. timer_ptr->timer_period = period_temp;
  184. timer_ptr->timer_func = cb_reg_array[i]->tmr_cb_ptr;
  185. timer_ptr->timer_arg = arg_temp;
  186. //add timer to suspended_timer_list
  187. if(suspended_timer_list_head == NULL){
  188. suspended_timer_list_head = timer_ptr;
  189. suspended_timer_list_tail = timer_ptr;
  190. }
  191. else{
  192. suspended_timer_list_tail->timer_next = timer_ptr;
  193. suspended_timer_list_tail = timer_ptr;
  194. }
  195. }
  196. }
  197. }
  198. //if timer was suspended, timer_ptr->timer_next is invalid, use next_timer instead.
  199. if(next_timer != (os_timer_t*)0xffffffff){
  200. timer_ptr = next_timer;
  201. }
  202. else{
  203. timer_ptr = timer_ptr->timer_next;
  204. }
  205. }
  206. //tmr_cb_ptr_array is no longer needed.
  207. free(cb_reg_array);
  208. //add suspended_timer_list pointer to swtimer table.
  209. lua_pushstring(L, SUSP_LIST_STR);
  210. lua_pushlightuserdata(L, suspended_timer_list_head);
  211. lua_rawset(L, -3);
  212. //pop swtimer table from stack
  213. lua_pop(L, 1);
  214. return;
  215. }
  216. void swtmr_resume_timers(){
  217. lua_State* L = lua_getstate();
  218. //get swtimer table
  219. get_swtmr_registry(L);
  220. if(!lua_istable(L, -1)) {lua_pop(L, 1); return;}
  221. //get suspended_timer_list lightuserdata
  222. lua_pushstring(L, SUSP_LIST_STR);
  223. lua_rawget(L, -2);
  224. //check for existence of the suspended_timer_list pointer userdata, return if not found
  225. if(!lua_isuserdata(L, -1)) {lua_pop(L, 2); return;}
  226. os_timer_t* suspended_timer_list_ptr = lua_touserdata(L, -1);
  227. lua_pop(L, 1); //pop suspended timer list userdata from stack
  228. //since timers will be resumed, the suspended_timer_list lightuserdata can be cleared from swtimer table
  229. lua_pushstring(L, SUSP_LIST_STR);
  230. lua_pushnil(L);
  231. lua_rawset(L, -3);
  232. lua_pop(L, 1); //pop swtimer table from stack
  233. volatile uint32 frc2_count = RTC_REG_READ(FRC2_COUNT_ADDRESS);
  234. //this section does the actual resuming of the suspended timer(s)
  235. while(suspended_timer_list_ptr != NULL){
  236. os_timer_t* timer_list_ptr = timer_list;
  237. //the pointer to next suspended timer must be saved, the current suspended timer will be removed from the list
  238. os_timer_t* next_suspended_timer_ptr = suspended_timer_list_ptr->timer_next;
  239. suspended_timer_list_ptr->timer_expire += frc2_count;
  240. //traverse timer_list to determine where to insert suspended timer
  241. while(timer_list_ptr != NULL){
  242. if(suspended_timer_list_ptr->timer_expire > timer_list_ptr->timer_expire){
  243. if(timer_list_ptr->timer_next != NULL){
  244. //current timer is not at tail of timer_list
  245. if(suspended_timer_list_ptr->timer_expire < timer_list_ptr->timer_next->timer_expire){
  246. //insert suspended timer between current timer and next timer
  247. suspended_timer_list_ptr->timer_next = timer_list_ptr->timer_next;
  248. timer_list_ptr->timer_next = suspended_timer_list_ptr;
  249. break; //timer resumed exit while loop
  250. }
  251. else{
  252. //suspended timer expire is larger than next timer
  253. }
  254. }
  255. else{
  256. //current timer is at tail of timer_list and suspended timer expire is greater then current timer
  257. //append timer to end of timer_list
  258. timer_list_ptr->timer_next = suspended_timer_list_ptr;
  259. suspended_timer_list_ptr->timer_next = NULL;
  260. break; //timer resumed exit while loop
  261. }
  262. }
  263. else if(timer_list_ptr == timer_list){
  264. //insert timer at head of list
  265. suspended_timer_list_ptr->timer_next = timer_list_ptr;
  266. timer_list = timer_list_ptr = suspended_timer_list_ptr;
  267. break; //timer resumed exit while loop
  268. }
  269. //suspended timer expire is larger than next timer
  270. //timer not resumed, next timer in timer_list
  271. timer_list_ptr = timer_list_ptr->timer_next;
  272. }
  273. //timer was resumed, next suspended timer
  274. suspended_timer_list_ptr = next_suspended_timer_ptr;
  275. }
  276. return;
  277. }
  278. //this function registers a timer callback pointer in a lua table
  279. void swtmr_cb_register(void* timer_cb_ptr, uint8 suspend_policy){
  280. lua_State* L = lua_getstate();
  281. if(!L){
  282. // If Lua has not started yet, then add timer cb to queue for later processing after Lua has started
  283. add_to_reg_queue(timer_cb_ptr, suspend_policy);
  284. return;
  285. }
  286. if(timer_cb_ptr){
  287. size_t cb_list_last_idx = 0;
  288. get_swtmr_registry(L);
  289. if(!lua_istable(L, -1)){
  290. //swtmr does not exist, create and add to registry and leave table as ToS
  291. lua_pop(L, 1);
  292. lua_newtable(L);
  293. lua_pushvalue(L, -1);
  294. set_swtmr_registry(L);
  295. }
  296. lua_pushstring(L, CB_LIST_STR);
  297. lua_rawget(L, -2);
  298. if(lua_istable(L, -1)){
  299. //cb_list exists, get length of list
  300. cb_list_last_idx = lua_objlen(L, -1);
  301. }
  302. else{
  303. //cb_list does not exist in swtmr, create and add to swtmr
  304. lua_pop(L, 1);// pop nil value from stack
  305. lua_newtable(L);//create new table for swtmr.timer_cb_list
  306. lua_pushstring(L, CB_LIST_STR); //push name for the new table onto the stack
  307. lua_pushvalue(L, -2); //push table to top of stack
  308. lua_rawset(L, -4); //pop table and name from stack and register in swtmr
  309. }
  310. //append new timer cb ptr to table
  311. lua_pushnumber(L, cb_list_last_idx+1);
  312. cb_registry_item_t* reg_item = lua_newuserdata(L, sizeof(cb_registry_item_t));
  313. reg_item->tmr_cb_ptr = timer_cb_ptr;
  314. reg_item->suspend_policy = suspend_policy;
  315. lua_rawset(L, -3);
  316. //clear items pushed onto stack by this function
  317. lua_pop(L, 2);
  318. }
  319. return;
  320. }
  321. //this function adds the timer cb ptr to a queue for later registration after lua has started
  322. static void add_to_reg_queue(void* timer_cb_ptr, uint8 suspend_policy){
  323. if(!timer_cb_ptr)
  324. return;
  325. tmr_cb_queue_t* queue_temp = calloc(1,sizeof(tmr_cb_queue_t));
  326. if(!queue_temp){
  327. //it's boot time currently and we're already out of memory, something is very wrong...
  328. dbg_printf("\n\t%s:out of memory, rebooting.", __FUNCTION__);
  329. system_restart();
  330. }
  331. queue_temp->tmr_cb_ptr = timer_cb_ptr;
  332. queue_temp->suspend_policy = suspend_policy;
  333. queue_temp->next = NULL;
  334. if(register_queue == NULL){
  335. register_queue = queue_temp;
  336. }
  337. else{
  338. tmr_cb_queue_t* queue_ptr = register_queue;
  339. while(queue_ptr->next != NULL){
  340. queue_ptr = queue_ptr->next;
  341. }
  342. queue_ptr->next = queue_temp;
  343. }
  344. if(!cb_register_task_id){
  345. cb_register_task_id = task_get_id(process_cb_register_queue);//get task id from task interface
  346. task_post_low(cb_register_task_id, false); //post task to process next item in queue
  347. }
  348. return;
  349. }
  350. static void process_cb_register_queue(task_param_t param, uint8 priority)
  351. {
  352. if(!lua_getstate()){
  353. SWTMR_DBG("L== NULL, Lua not yet started! posting task");
  354. task_post_low(cb_register_task_id, false); //post task to process next item in queue
  355. return;
  356. }
  357. while(register_queue != NULL){
  358. tmr_cb_queue_t* register_queue_ptr = register_queue;
  359. void* cb_ptr_tmp = register_queue_ptr->tmr_cb_ptr;
  360. swtmr_cb_register(cb_ptr_tmp, register_queue_ptr->suspend_policy);
  361. register_queue = register_queue->next;
  362. free(register_queue_ptr);
  363. }
  364. return;
  365. }
  366. #ifdef SWTMR_DEBUG
  367. int print_timer_list(lua_State* L){
  368. get_swtmr_registry(L);
  369. if(!lua_istable(L, -1)) {lua_pop(L, 1); return 0;}
  370. lua_pushstring(L, CB_LIST_STR);
  371. lua_rawget(L, -2);
  372. if(!lua_istable(L, -1)) {lua_pop(L, 2); return 0;}
  373. os_timer_t* suspended_timer_list_head = NULL;
  374. os_timer_t* suspended_timer_list_tail = NULL;
  375. lua_pushstring(L, SUSP_LIST_STR);
  376. lua_rawget(L, -3);
  377. if(lua_isuserdata(L, -1)){
  378. suspended_timer_list_head = suspended_timer_list_tail = lua_touserdata(L, -1);
  379. while(suspended_timer_list_tail->timer_next != NULL){
  380. suspended_timer_list_tail = suspended_timer_list_tail->timer_next;
  381. }
  382. }
  383. lua_pop(L, 1);
  384. size_t registered_cb_qty = lua_objlen(L, -1);
  385. cb_registry_item_t** cb_reg_array = calloc(1,sizeof(cb_registry_item_t*)*registered_cb_qty);
  386. if(!cb_reg_array){
  387. luaL_error(L, "%s: unable to suspend timers, out of memory!", __func__);
  388. return 0;
  389. }
  390. uint8 index = 0;
  391. lua_pushnil(L);
  392. while(lua_next(L, -2) != 0){
  393. if(lua_isuserdata(L, -1)){
  394. cb_reg_array[index] = lua_touserdata(L, -1);
  395. }
  396. lua_pop(L, 1);
  397. index++;
  398. }
  399. lua_pop(L, 1);
  400. os_timer_t* timer_list_ptr = timer_list;
  401. dbg_printf("\n\tCurrent FRC2: %u\n", RTC_REG_READ(FRC2_COUNT_ADDRESS));
  402. dbg_printf("\ttimer_list:\n");
  403. while(timer_list_ptr != NULL){
  404. bool registered_flag = FALSE;
  405. for(int i=0; i < registered_cb_qty; i++){
  406. if(timer_list_ptr->timer_func == cb_reg_array[i]->tmr_cb_ptr){
  407. registered_flag = TRUE;
  408. break;
  409. }
  410. }
  411. dbg_printf("\tptr:%p\tcb:%p\texpire:%8u\tperiod:%8u\tnext:%p\t%s\n",
  412. timer_list_ptr, timer_list_ptr->timer_func, timer_list_ptr->timer_expire, timer_list_ptr->timer_period, timer_list_ptr->timer_next, registered_flag ? "Registered" : "");
  413. timer_list_ptr = timer_list_ptr->timer_next;
  414. }
  415. free(cb_reg_array);
  416. lua_pop(L, 1);
  417. return 0;
  418. }
  419. int print_susp_timer_list(lua_State* L){
  420. get_swtmr_registry(L);
  421. if(!lua_istable(L, -1)){
  422. return luaL_error(L, "swtmr table not found!");
  423. }
  424. lua_pushstring(L, SUSP_LIST_STR);
  425. lua_rawget(L, -2);
  426. if(!lua_isuserdata(L, -1)){
  427. return luaL_error(L, "swtmr.suspended_list userdata not found!");
  428. }
  429. os_timer_t* susp_timer_list_ptr = lua_touserdata(L, -1);
  430. dbg_printf("\n\tsuspended_timer_list:\n");
  431. while(susp_timer_list_ptr != NULL){
  432. dbg_printf("\tptr:%p\tcb:%p\texpire:%8u\tperiod:%8u\tnext:%p\n",susp_timer_list_ptr,
  433. susp_timer_list_ptr->timer_func, susp_timer_list_ptr->timer_expire,
  434. susp_timer_list_ptr->timer_period, susp_timer_list_ptr->timer_next);
  435. susp_timer_list_ptr = susp_timer_list_ptr->timer_next;
  436. }
  437. return 0;
  438. }
  439. int suspend_timers_lua(lua_State* L){
  440. swtmr_suspend_timers();
  441. return 0;
  442. }
  443. int resume_timers_lua(lua_State* L){
  444. swtmr_resume_timers();
  445. return 0;
  446. }
  447. LROT_BEGIN(test_swtimer_debug, NULL, 0)
  448. LROT_FUNCENTRY( timer_list, print_timer_list )
  449. LROT_FUNCENTRY( susp_timer_list, print_susp_timer_list )
  450. LROT_FUNCENTRY( suspend, suspend_timers_lua )
  451. LROT_FUNCENTRY( resume, resume_timers_lua )
  452. LROT_END(test_swtimer_debug, NULL, 0)
  453. NODEMCU_MODULE(SWTMR_DBG, "SWTMR_DBG", test_swtimer_debug, NULL);
  454. #endif /* SWTMR_DEBUG */