swtimer.c 18 KB

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