diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index f84651b04..091776ea5 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -653,8 +653,9 @@ void Debugger::dumpEvents(long start, long size) const { CallbackHandler::event_begin() + end, [this, &index, &end](const Event &e) { this->channel->write( - R"({"topic": "%s", "payload": "%s"})", - e.topic.c_str(), e.payload.c_str()); + R"({"topic": "%s", "group": %d, "payload": "%s"})", + e.topic.c_str(), static_cast(e.group), + e.payload.c_str()); if (++index < end) { this->channel->write(", "); } @@ -769,7 +770,8 @@ bool Debugger::handlePushedEvent(char *bytes) const { if (*bytes != interruptPUSHEvent) return false; auto parsed = nlohmann::json::parse(bytes + 1); debug("handle pushed event: %s\n", bytes + 1); - auto *event = new Event(*parsed.find("topic"), *parsed.find("payload")); + auto *event = new Event(*parsed.find("topic"), *parsed.find("group"), + *parsed.find("payload")); CallbackHandler::push_event(event); this->notifyPushedEvent(); return true; @@ -1409,6 +1411,10 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { topic[topicSize] = '\0'; program_state += topicSize; + // read group + uint8_t group_idx = read_B32(&program_state); + EventGroup group = static_cast(group_idx); + // read payload uint32_t payloadSize = read_B32(&program_state); auto *payload = @@ -1417,7 +1423,8 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { payload[payloadSize] = '\0'; program_state += payloadSize; - CallbackHandler::push_event(topic, payload, payloadSize); + CallbackHandler::push_event(topic, group, payload, + payloadSize); free(topic); } break; diff --git a/src/Edward/proxy_supervisor.cpp b/src/Edward/proxy_supervisor.cpp index 511efca99..d02cbe07a 100644 --- a/src/Edward/proxy_supervisor.cpp +++ b/src/Edward/proxy_supervisor.cpp @@ -50,7 +50,7 @@ Event *parseJSON(char *buff) { nlohmann::basic_json<> parsed = nlohmann::json::parse(buff); printf("parseJSON: %s\n", parsed.dump().c_str()); std::string payload = *parsed.find("payload"); - return new Event(*parsed.find("topic"), payload); + return new Event(*parsed.find("topic"), *parsed.find("group"), payload); } ProxySupervisor::ProxySupervisor(Channel *duplex, warduino::mutex *mutex) { @@ -106,8 +106,9 @@ void ProxySupervisor::listenToSocket() { debug("parseJSON: %s\n", parsed.dump().c_str()); if (isEvent(parsed)) { - CallbackHandler::push_event(new Event( - *parsed.find("topic"), *parsed.find("payload"))); + CallbackHandler::push_event( + new Event(*parsed.find("topic"), *parsed.find("group"), + *parsed.find("payload"))); WARDuino::instance()->debugger->notifyPushedEvent(); } diff --git a/src/Primitives/arduino.cpp b/src/Primitives/arduino.cpp index 803295737..012a6798f 100644 --- a/src/Primitives/arduino.cpp +++ b/src/Primitives/arduino.cpp @@ -84,7 +84,8 @@ int isr_index = 0; /* Private macro to create an ISR for a specific pin*/ #define def_isr(pin) \ void isr_##pin() { \ - CallbackHandler::push_event(INTERRUPT_TOPIC_PREFIX #pin, "", 0); \ + CallbackHandler::push_event(INTERRUPT_TOPIC_PREFIX #pin, \ + EventGroup::INTERRUPT_EVENT, "", 0); \ } /* Common GPIO pins on ESP32 devices:*/ @@ -129,7 +130,7 @@ int resolve_isr(int pin) { return -1; } -#define NUM_GLOBALS 0 +#define NUM_GLOBALS 5 #define ALL_GLOBALS NUM_GLOBALS int global_index = 0; @@ -523,6 +524,31 @@ def_prim(unsubscribe_interrupt, oneToNoneU32) { return true; } +// Interrupt masking + +def_prim(mask_interrupts, twoToNoneU32) { + uint8_t discard = arg0.uint32; + uint8_t group = arg1.uint32; + debug("EMU: mask_interrupt(%u, %u) \n", discard, group); + pop_args(2); + uint32_t key = CallbackHandler::mask_interrupt( + static_cast(group), discard); + pushUInt32(key); + return true; +} + +def_prim(unmask_interrupts, oneToNoneU32) { + uint8_t key = arg0.uint32; + debug("EMU: unmask_interrupt(%u) \n", key); + pop_args(1); + CallbackHandler::unmask_interrupt(key); + return true; +} + +def_glob(event_groups_all, I32, false, 0xffffffff); +def_glob(event_group_interrupt, I32, false, EventGroup::INTERRUPT_EVENT); +def_glob(event_group_mqtt, I32, false, EventGroup::MQTT_EVENT); + // MQTT MODULE #include @@ -541,7 +567,8 @@ def_prim(mqtt_init, threeToNoneU32) { mqttClient.setServer(server, port); mqttClient.setCallback([](const char *topic, const unsigned char *payload, unsigned int length) { - CallbackHandler::push_event(topic, (const char *)payload, length); + CallbackHandler::push_event(topic, EventGroup::MQTT_EVENT, + (const char *)payload, length); }); #if DEBUG @@ -863,6 +890,13 @@ void install_primitives(Interpreter *interpreter) { install_primitive(chip_ledc_attach); install_primitive(chip_ledc_set_duty); + install_primitive(mask_interrupts); + install_primitive(unmask_interrupts); + + install_global(event_groups_all); + install_global(event_group_interrupt); + install_global(event_group_mqtt); + dbg_info("INSTALLING ISRs\n"); install_isrs(); } diff --git a/src/Primitives/emulated.cpp b/src/Primitives/emulated.cpp index 94bccf83a..d0d8860cc 100644 --- a/src/Primitives/emulated.cpp +++ b/src/Primitives/emulated.cpp @@ -26,7 +26,7 @@ #include "../WARDuino/CallbackHandler.h" #include "primitives.h" -#define NUM_GLOBALS 0 +#define NUM_GLOBALS 5 #define ALL_GLOBALS NUM_GLOBALS int global_index = 0; @@ -103,8 +103,8 @@ def_prim(test, oneToNoneU32) { Callback c = Callback(m, topic, fidx); CallbackHandler::add_callback(c); auto *payload = reinterpret_cast("TestPayload"); - CallbackHandler::push_event(topic, reinterpret_cast(payload), - 11); + CallbackHandler::push_event(topic, EventGroup::INTERRUPT_EVENT, + reinterpret_cast(payload), 11); pop_args(1); return true; } @@ -462,6 +462,31 @@ def_prim(chip_ledc_attach_pin, twoToNoneU32) { return true; } +// Interrupt masking + +def_prim(mask_interrupts, twoToNoneU32) { + uint8_t discard = arg0.uint32; + uint8_t group = arg1.uint32; + debug("EMU: mask_interrupt(%u, %u) \n", discard, group); + pop_args(2); + uint32_t key = CallbackHandler::mask_interrupt( + static_cast(group), discard); + pushUInt32(key); + return true; +} + +def_prim(unmask_interrupts, oneToNoneU32) { + uint8_t key = arg0.uint32; + debug("EMU: unmask_interrupt(%u) \n", key); + pop_args(1); + CallbackHandler::unmask_interrupt(key); + return true; +} + +def_glob(event_groups_all, I32, false, 0xffffffff); +def_glob(event_group_interrupt, I32, false, EventGroup::INTERRUPT_EVENT); +def_glob(event_group_mqtt, I32, false, EventGroup::MQTT_EVENT); + //------------------------------------------------------ // Installing all the primitives //------------------------------------------------------ @@ -516,6 +541,15 @@ void install_primitives(Interpreter *interpreter) { install_primitive(read_uart_sensor); install_primitive(nxt_touch_sensor); install_primitive(ev3_touch_sensor); + + // Interrupt masking & disabling + install_primitive(mask_interrupts); + install_primitive(unmask_interrupts); + install_primitive(test); + + install_global(event_groups_all); + install_global(event_group_interrupt); + install_global(event_group_mqtt); } Memory external_mem{}; diff --git a/src/Primitives/idf.cpp b/src/Primitives/idf.cpp index 09ea1d8d1..685223952 100644 --- a/src/Primitives/idf.cpp +++ b/src/Primitives/idf.cpp @@ -24,10 +24,11 @@ #include "../Memory/mem.h" #include "../Utils/macros.h" #include "../Utils/util.h" +#include "../WARDuino/CallbackHandler.h" #include "driver/gpio.h" #include "primitives.h" -#define NUM_GLOBALS 0 +#define NUM_GLOBALS 5 #define ALL_GLOBALS NUM_GLOBALS int global_index = 0; @@ -76,6 +77,31 @@ def_prim(chip_digital_read, oneToOneU32) { return true; } +// Interrupt masking + +def_prim(mask_interrupts, twoToNoneU32) { + uint8_t discard = arg0.uint32; + uint8_t group = arg1.uint32; + debug("EMU: mask_interrupt(%u, %u) \n", discard, group); + pop_args(2); + uint32_t key = CallbackHandler::mask_interrupt( + static_cast(group), discard); + pushUInt32(key); + return true; +} + +def_prim(unmask_interrupts, oneToNoneU32) { + uint8_t key = arg0.uint32; + debug("EMU: unmask_interrupt(%u) \n", key); + pop_args(1); + CallbackHandler::unmask_interrupt(key); + return true; +} + +def_glob(event_groups_all, I32, false, 0xffffffff); +def_glob(event_group_interrupt, I32, false, EventGroup::INTERRUPT_EVENT); +def_glob(event_group_mqtt, I32, false, EventGroup::MQTT_EVENT); + //------------------------------------------------------ // Installing all the primitives //------------------------------------------------------ @@ -85,6 +111,13 @@ void install_primitives(Interpreter *interpreter) { install_primitive(chip_pin_mode); install_primitive(chip_digital_write); install_primitive(chip_digital_read); + + install_primitive(mask_interrupts); + install_primitive(unmask_interrupts); + + install_global(event_groups_all); + install_global(event_group_interrupt); + install_global(event_group_mqtt); } Memory external_mem = {0, 0, 0, nullptr}; diff --git a/src/Primitives/zephyr.cpp b/src/Primitives/zephyr.cpp index cb02d67fc..b9ca7ef64 100644 --- a/src/Primitives/zephyr.cpp +++ b/src/Primitives/zephyr.cpp @@ -31,11 +31,12 @@ #include "../Memory/mem.h" #include "../Utils/macros.h" #include "../Utils/util.h" +#include "../WARDuino/CallbackHandler.h" #include "Mindstorms/Motor.h" #include "Mindstorms/uart_sensor.h" #include "primitives.h" -#define NUM_GLOBALS 0 +#define NUM_GLOBALS 5 #define ALL_GLOBALS NUM_GLOBALS int global_index = 0; @@ -501,6 +502,31 @@ def_prim(display_draw_string, sevenToNoneU32) { } #endif +// Interrupt masking + +def_prim(mask_interrupts, twoToNoneU32) { + uint8_t discard = arg0.uint32; + uint8_t group = arg1.uint32; + debug("EMU: mask_interrupt(%u, %u) \n", discard, group); + pop_args(2); + uint32_t key = CallbackHandler::mask_interrupt( + static_cast(group), discard); + pushUInt32(key); + return true; +} + +def_prim(unmask_interrupts, oneToNoneU32) { + uint8_t key = arg0.uint32; + debug("EMU: unmask_interrupt(%u) \n", key); + pop_args(1); + CallbackHandler::unmask_interrupt(key); + return true; +} + +def_glob(event_groups_all, I32, false, 0xffffffff); +def_glob(event_group_interrupt, I32, false, EventGroup::INTERRUPT_EVENT); +def_glob(event_group_mqtt, I32, false, EventGroup::MQTT_EVENT); + //------------------------------------------------------ // Installing all the primitives //------------------------------------------------------ @@ -516,6 +542,13 @@ void install_primitives(Interpreter *interpreter) { install_primitive(print_int); install_primitive(abort); + install_primitive(mask_interrupts); + install_primitive(unmask_interrupts); + + install_global(event_groups_all); + install_global(event_group_interrupt); + install_global(event_group_mqtt); + #ifdef CONFIG_BOARD_STM32L496G_DISCO install_primitive(drive_motor); install_primitive(stop_motor); diff --git a/src/WARDuino/CallbackHandler.cpp b/src/WARDuino/CallbackHandler.cpp index 0c87d76ff..1894a4a9f 100644 --- a/src/WARDuino/CallbackHandler.cpp +++ b/src/WARDuino/CallbackHandler.cpp @@ -67,15 +67,98 @@ size_t CallbackHandler::callback_count(const std::string &topic) { return callbacks->find(topic)->second->size(); } +// key counter starts at 1 +// reset every time the interrupt_mask_topic_to_keys is emptied +uint32_t CallbackHandler::interrupt_mask_fresh_key = 1; + +// map of < event group , ( set of disable keys , set of discard keys ) > +std::unordered_map, + std::unordered_set>> + *CallbackHandler::event_group_to_keys = + new std::unordered_map, + std::unordered_set>>(); + +// ( set of disable keys , set of discard keys ) that impact ALL events +std::pair, std::unordered_set> + *CallbackHandler::all_event_groups_keys = + new std::pair, + std::unordered_set>(); + +uint32_t CallbackHandler::mask_interrupt(EventGroup group, bool discard) { + uint32_t key = CallbackHandler::interrupt_mask_fresh_key++; + bool all_events = static_cast(group) == 0xff; + auto &pair_of_keys = + all_events ? *all_event_groups_keys : (*event_group_to_keys)[group]; + std::unordered_set &event_keys = + discard ? pair_of_keys.second : pair_of_keys.first; + event_keys.insert(key); + printf("Masked interrupt with group %u using key %u (discard: %u)\n", group, + key, discard); + printf( + "All event groups keys now has %zu disable keys and %zu discard keys\n", + all_event_groups_keys->first.size(), + all_event_groups_keys->second.size()); + return key; +} + +void CallbackHandler::unmask_interrupt(uint32_t key) { + std::vector groups_to_remove; + + for (auto *keys : + {&all_event_groups_keys->first, &all_event_groups_keys->second}) { + if (keys->erase(key)) { + return; + } + } + + for (auto &[group, pair] : *event_group_to_keys) { + for (auto *keys : {&pair.first, &pair.second}) { + if (keys->erase(key)) { + if (pair.first.empty() && pair.second.empty()) { + groups_to_remove.push_back(group); + } + break; + } + } + } + + for (auto g : groups_to_remove) { + event_group_to_keys->erase(g); + } +} + // WARNING: Push event functions should not use IO functions, since they can be // called from ISR callbacks -void CallbackHandler::push_event(std::string topic, const char *payload, +void CallbackHandler::push_event(std::string topic, EventGroup group, + const char *payload, const unsigned int length) { CallbackHandler::push_event( - new Event(std::move(topic), std::string(payload, length))); + new Event(std::move(topic), group, std::string(payload, length))); } void CallbackHandler::push_event(Event *event) { + // check if discarded for all events + auto &disable_all_groups_keys = all_event_groups_keys->second; + if (disable_all_groups_keys.size()) { + printf("Not pushing event with topic %s and group %u\n", + event->topic.c_str(), event->group); + return; + } + + // check if discarded for event group + auto entry = CallbackHandler::event_group_to_keys->find(event->group); + auto keys = entry != CallbackHandler::event_group_to_keys->end() + ? &entry->second.second + : nullptr; + if (keys != nullptr && + !keys->empty()) { // second check is actually redundant + debug("Not pushing event with topic %s and group %u\n", + event->topic.c_str(), event->group); + return; + } + + // push the event if not discarded if (events->size() < EVENTS_SIZE) { events->push_back(*event); } @@ -91,7 +174,29 @@ bool CallbackHandler::resolve_event(bool force) { } return false; } + + // check if masked for all events + auto &disable_all_groups_keys = all_event_groups_keys->first; + if (disable_all_groups_keys.size()) { + debug( + "Event with topic %s and group %u is masked for all event " + "groups.\n", + CallbackHandler::events->front().topic.c_str(), + CallbackHandler::events->front().group); + return false; + } + + // check if masked for event group Event event = CallbackHandler::events->front(); + auto entry = event_group_to_keys->find(event.group); + auto keys = + entry != event_group_to_keys->end() ? &entry->second.first : nullptr; + if (keys != nullptr && + !keys->empty()) { // second check is acutally redundant + debug("Event with topic %s and group %u is masked.\n", + event.topic.c_str(), event.group); + return false; + } if (should_push_event()) { Event e = CallbackHandler::events->at(CallbackHandler::pushed_cursor++); @@ -238,12 +343,14 @@ Callback::Callback(const Callback &c) { // Event class -Event::Event(std::string topic, std::string payload) { +Event::Event(std::string topic, EventGroup group, std::string payload) { this->topic = topic; + this->group = group; this->payload = payload; } std::string Event::serialized() const { - return R"({"topic": ")" + this->topic + R"(", "payload": ")" + - this->payload + R"("})"; + return R"({"topic": ")" + this->topic + R"(", "group": ")" + + std::to_string(static_cast(this->group)) + + R"(", "payload": ")" + this->payload + R"("})"; } diff --git a/src/WARDuino/CallbackHandler.h b/src/WARDuino/CallbackHandler.h index c9d40c937..fde8cc74d 100644 --- a/src/WARDuino/CallbackHandler.h +++ b/src/WARDuino/CallbackHandler.h @@ -4,18 +4,28 @@ #include #include #include +#include #include struct Module; class Callback; +enum EventGroup : uint8_t { + // do not rename to INTERRUPT (ESP32 headers define an INTERRUPT macro) + INTERRUPT_EVENT = 0, + MQTT_EVENT = 1, + + // 0xff is reserved (to denote all eventgroups), do not use +}; + class Event { public: std::string topic; + EventGroup group; std::string payload; - Event(std::string topic, std::string payload); + Event(std::string topic, EventGroup group, std::string payload); std::string serialized() const; }; @@ -23,6 +33,13 @@ class Event { class CallbackHandler { private: static std::unordered_map *> *callbacks; + static uint32_t interrupt_mask_fresh_key; + static std::unordered_map< + EventGroup, + std::pair, std::unordered_set>> + *event_group_to_keys; + static std::pair, std::unordered_set> + *all_event_groups_keys; static std::deque *events; CallbackHandler() = default; // Disallow creation @@ -42,12 +59,15 @@ class CallbackHandler { static std::string dump_callbacks(); static std::string dump_callbacksV2(bool includeOuterCurlyBraces = true); static size_t callback_count(const std::string &topic); - static void push_event(std::string topic, const char *payload, - unsigned int length); + static void push_event(std::string topic, EventGroup group, + const char *payload, unsigned int length); static void push_event(Event *event); static bool resolve_event(bool force = false); static bool manual_event_resolution; // do not resolve event automatically + + static uint32_t mask_interrupt(EventGroup group, bool discard); + static void unmask_interrupt(uint32_t key); }; class Callback { diff --git a/tutorials/wat/main/interrupt_masking.wast b/tutorials/wat/main/interrupt_masking.wast new file mode 100644 index 000000000..2d6b5227a --- /dev/null +++ b/tutorials/wat/main/interrupt_masking.wast @@ -0,0 +1,32 @@ +(module + (import "env" "mask_interrupts" (func $mask_interrupts (param i32 i32) (result i32))) + (import "env" "unmask_interrupts" (func $unmask_interrupts (param i32) )) + (import "env" "event_group_interrupt" (global $event_group_interrupt i32)) + + (import "env" "test" (func $test (param i32))) + + (import "env" "print_int" (func $print.int (param i32))) + + (func $main (export "main") + (local $key i32) + (local $i i32) + ;; mask interrupt with discarding and only the events grouped as interrupt + (local.set $key (call $mask_interrupts (global.get $event_group_interrupt) (i32.const 1))) + + ;; 20 loops: in each iteration the interrupt is triggered, but discarded because of the discarding mask + (local.set $i (i32.const 0)) + (loop $loop + (call $print.int (local.get $i)) + (call $test (i32.const <<<<<>>>>>)) ;; trigger the interrupt + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br_if $loop (i32.lt_s (local.get $i) (i32.const 20))) + ) + + ;; unmaks the interrupt using the returned key + (call $unmask_interrupts (local.get $key)) + ) + + (func $test_callback (export "test_callback") (param $payload i32) + (call $print.int (i32.const 424242)) + ) +)