#pragma once #include #include #include #include #include #include #include #include #include #include namespace interrupt { enum class resource_status : std::uint8_t { OFF, ON }; template concept has_enable_field = requires { Irq::enable_field; }; template concept has_resource = has_enable_field and not boost::mp11::mp_empty::value; template struct dynamic_controller { private: using all_resources_t = boost::mp11::mp_unique(Irqs const &...) { return boost::mp11::mp_append{}; }))>; template CONSTINIT static inline typename Register::DataType allowed_enables = std::numeric_limits::max(); template CONSTINIT static inline typename Register::DataType dynamic_enables{}; template CONSTINIT static inline bool is_resource_on = true; template struct doesnt_require_resource { template using fn = std::bool_constant and not boost::mp11::mp_contains< typename Irq::resources_t, Resource>::value>; }; template struct in_register { template using fn = std::is_same; }; /** * For each ResourceType, keep track of what interrupts can still be enabled * when that resource goes down. * * Each bit in this mask corresponds to an interrupt enable field in * RegType. If the bit is '1', that means the corresponding interrupt can be * enabled when the resource is not available. If the bit is '0', that means * the corresponding interrupt must be disabled when the resource is not * available. * * @tparam ResourceType * The resource we want to check. * * @tparam RegType * The specific register mask we want to check. */ template constexpr static typename RegType::DataType irqs_allowed = []() { // get all interrupt enable fields that don't require the given resource auto const matching_irqs = stdx::filter::template fn>( Root::descendants); auto const interrupt_enables_tuple = stdx::transform( [](auto irq) { return irq.enable_field; }, matching_irqs); // filter fields that aren't in RegType auto const fields_in_reg = stdx::filter::template fn>( interrupt_enables_tuple); // set the bits in the mask for interrupts that don't require the // resource using DataType = typename RegType::DataType; return fields_in_reg.fold_left( DataType{}, [](DataType value, auto field) -> DataType { return value | field.get_mask(); }); }(); template static inline void reprogram_interrupt_enables(RegTypeTuple regs) { stdx::for_each( [](R reg) { // make sure we don't enable any interrupts that are not allowed // according to resource availability auto const final_enables = allowed_enables & dynamic_enables; // update the hardware registers apply(write(reg.raw(final_enables))); }, regs); } /** * tuple of every interrupt register affected by a resource */ template using has_resource_t = std::bool_constant>; constexpr static auto all_resource_affected_regs = stdx::to_unsorted_set(stdx::transform( [](Irq) { return Irq::enable_field.get_register(); }, stdx::filter(Root::descendants))); /** * Reprogram interrupt enables based on updated resource availability. */ static inline auto recalculate_allowed_enables() { // set allowed_enables mask for each resource affected register stdx::for_each( [](R) { using DataType = typename R::DataType; allowed_enables = std::numeric_limits::max(); }, all_resource_affected_regs); // for each resource, if it is not on, mask out unavailable interrupts stdx::template_for_each([]() { if (not is_resource_on) { stdx::for_each( [](R) { allowed_enables &= irqs_allowed; }, all_resource_affected_regs); } }); return all_resource_affected_regs; } /** * Store the interrupt enable values that FW _wants_ at runtime, * irrespective of any resource conflicts that would require specific * interrupts to be disabled. * * @tparam RegType * The croo::Register this value corresponds to. */ template struct match_flow { template using fn = std::bool_constant and (... or Irq::template triggers_flow)>; }; template static inline void enable_by_name() { // NOTE: critical section is not needed here because shared state is // only updated by the final call to enable_by_field // TODO: add support to enable/disable top-level IRQs by name. // this will require another way to manage them vs. mmio // registers. once that goes in, then enable_by_field should be // removed or made private. auto const matching_irqs = stdx::filter::template fn>(Root::descendants); auto const interrupt_enables_tuple = stdx::transform( [](auto irq) { return irq.enable_field; }, matching_irqs); interrupt_enables_tuple.apply([](Fields...) { enable_by_field(); }); } template static inline void update_resource(resource_status status) { conc::call_in_critical_section([&] { is_resource_on = (status == resource_status::ON); recalculate_allowed_enables(); reprogram_interrupt_enables(all_resource_affected_regs); }); } public: template static inline void turn_on_resource() { update_resource(resource_status::ON); } template static inline void turn_off_resource() { update_resource(resource_status::OFF); } template static inline void enable_by_field() { conc::call_in_critical_section([] { [[maybe_unused]] auto const enable = []() -> void { using R = typename F::RegisterType; if constexpr (Enable) { dynamic_enables |= F::get_mask(); } else { dynamic_enables &= ~F::get_mask(); } }; (enable.template operator()(), ...); auto const unique_regs = stdx::to_unsorted_set( stdx::tuple{}); reprogram_interrupt_enables(unique_regs); }); } template static inline void enable() { enable_by_name(); } template static inline void disable() { enable_by_name(); } }; } // namespace interrupt