#define pr_fmt(fmt) "[hui] " fmt #include "events.h" #include "hui_internal.h" #include "log.h" #include #include #include #include #define DEFINE_EVENT_STATE(_name, ...) __VA_ARGS__ #define DEFINE_EVENT(_name, _id, ...) \ const struct avm_hui_event_info __event_info_##_name = { \ .id = _id, \ .name = #_name, \ }; #define DEFINE_EVENT_RAW(_name, _id, ...) DEFINE_EVENT(_name, _id) #define DEFINE_EVENT_RAW_INTERNAL(_name, ...) DEFINE_EVENT(_name, -1) #define _ALL_EVENTS #include const struct avm_hui_event_info *_avm_hui_all_events[] = { #define DEFINE_EVENT_STATE(_name, ...) __VA_ARGS__ #define DEFINE_EVENT(_name, _id, ...) &__event_info_##_name, #define DEFINE_EVENT_RAW(_name, _id, ...) DEFINE_EVENT(_name, _id) #define DEFINE_EVENT_RAW_INTERNAL(_name, ...) DEFINE_EVENT(_name, -1) #define _ALL_EVENTS #include NULL, }; #define DEFINE_EVENT(_name, _id, ...) \ [_name] = { .name = #_name, .event_id = _id, __VA_ARGS__ }, #define DEFINE_EVENT_STATE(_name, ...) \ static const struct event_member_def \ __event_def_##_name[_name##_invalid] = { __VA_ARGS__ }; \ struct event __event_##_name = { \ .name = #_name, \ .max_state = _name##_invalid, \ .def = __event_def_##_name, \ .raw_event_id = 0, \ }; #define DEFINE_EVENT_RAW(_name, _id, _default) \ struct event __event_##_name = { \ .name = #_name, \ .max_state = 0, \ .def = NULL, \ .raw_event_id = _id, \ .current_value = ATOMIC_INIT(_default), \ .pending_value = ATOMIC_INIT(_default), \ .callback = NULL, \ }; #define DEFINE_EVENT_RAW_INTERNAL(_name, ...) DEFINE_EVENT_RAW(_name, -1, 0) #include static struct event *events_list[] = { #define DEFINE_EVENT_STATE(_name, ...) &__event_##_name, #define DEFINE_EVENT_RAW(_name, ...) &__event_##_name, #define DEFINE_EVENT_RAW_INTERNAL(_name, ...) &__event_##_name, #include NULL, }; static inline void events_handle_timeout(timer_callback j) { pr_debug("Events timeout reached\n"); hui_schedule_update(); } static HUI_DEFINE_TIMER(events_timer, events_handle_timeout); /* * This function is the main entry point for led events delivered via avm_event. It * will try to find the corresponding event group or raw event. * * If the new event is the same as currently set, the function will short-circuit and * no further processing done. Transient events are exempt from this. * * The new event value will be stored as pending and a hui update cycle is * requested. During the update cycle the pending event will update the event state. */ static inline void event_handler(void *ctx, unsigned char *buf, unsigned int len) { int i, j; int event_id, value; struct _avm_event_led_status *incoming_event; if (len < sizeof(struct _avm_event_led_status)) return; incoming_event = (void *)buf; if (incoming_event->header.id != avm_event_id_led_status) return; event_id = incoming_event->led; value = incoming_event->state; for (i = 0; events_list[i] != NULL; i++) { struct event *event = events_list[i]; if (event->def) { for (j = 0; j < event->max_state; j++) { if (event->def[j].event_id != event_id) continue; if (!event_is_transient(event) && event_get_value(event) == j && event_get_pending_value(event) == j) return; pr_debug( "Got event state %s with member %s (%d) and value %d\n", event->name, event->def[j].name, event_id, value); atomic_set(&event->pending_value, j); hui_log_event(event, j); hui_schedule_update(); return; } } else if (event->raw_event_id == event_id) { if (!event_is_transient(event) && event_get_value(event) == value) return; pr_debug("Got raw event %s with value %d\n", event->name, value); atomic_set(&event->pending_value, value); hui_log_event(event, value); hui_schedule_update(); return; } } pr_info("Unhandled event %d with value %d\n", event_id, value); hui_log_unknown_event(event_id, value); } static inline void events_expire(void) { int i; for (i = 0; events_list[i] != NULL; i++) { struct event *event = events_list[i]; // No member defs, can not have a timeout if (!event->def) continue; // No timeout configured if (!event->timeout_setup) continue; pr_debug("Look at timeout for %s: %ld <> %ld\n", event->name, event->timeout, jiffies); // We expired if (time_is_before_eq_jiffies(event->timeout)) { int value = event_get_value(event); int new = event->def[value].timeout.next; pr_debug("Expire timeout %s: %s (%d) -> %s (%d)\n", event->name, event->def[value].name, value, event->def[new].name, new); atomic_set(&event->pending_value, new); hui_log_event(event, new); } } } static inline void events_update_values(void) { int i; for (i = 0; events_list[i] != NULL; i++) { struct event *event = events_list[i]; int old = event_get_value(event); int new = event_get_pending_value(event); if (!event_is_transient(event) && new == old) continue; if (event->def) pr_debug("Update %s: %s (%d) -> %s (%d)\n", event->name, event->def[old].name, old, event->def[new].name, new); else if (event_is_transient(event)) pr_debug("Got %s with %d\n", event->name, new); else pr_debug("Update %s: %d -> %d\n", event->name, old, new); atomic_set(&event->current_value, new); event->timeout = 0; event->timeout_setup = 0; if (event->callback) event->callback(event, old, new); } } static inline unsigned long min_jiffies(unsigned long a, unsigned long b) { if (a == MAX_JIFFY_OFFSET) return b; if (b == MAX_JIFFY_OFFSET) return a; // TODO: is this correct? return time_before(a, b) ? a : b; } static inline void events_setup_timeout(void) { int i; unsigned long min = MAX_JIFFY_OFFSET; for (i = 0; events_list[i] != NULL; i++) { struct event *event = events_list[i]; const struct event_member_def *def; // Not a state event if (!event->def) continue; def = event->def + event_get_value(event); // Is this state supposed to have an timeout if (def->timeout.ms == 0) continue; // No timeout setup, do so if (!event->timeout_setup) { event->timeout = jiffies + msecs_to_jiffies(def->timeout.ms); event->timeout_setup = 1; pr_debug("Setup timeout for %s: expires=%ld\n", event->name, event->timeout); } min = min_jiffies(min, event->timeout); } // No timeouts, no need to reschedule if (min == MAX_JIFFY_OFFSET) return; pr_debug("Schedule timeout for %ld\n", min + 1); mod_timer(&events_timer, min + 1); } void events_update(void) { events_expire(); events_update_values(); events_setup_timeout(); } static void proc_event_state(struct seq_file *seq, void *context) { int i; mutex_lock(&hui_update_mutex); for (i = 0; events_list[i] != NULL; i++) { struct event *event = events_list[i]; int value = event_get_value(event); if (event_is_transient(event)) continue; if (event->def) { seq_printf(seq, "%s: %s (%d)\n", event->name, event->def[value].name, value); } else { seq_printf(seq, "%s(%d): %d\n", event->name, event->raw_event_id, value); } } mutex_unlock(&hui_update_mutex); } void *event_sink; void *event_source; int _avm_hui_send_event(int version, enum _led_event event_id, unsigned int value) { struct _avm_event_led_status *event; int size = sizeof(struct _avm_event_led_status); if (hui_bare_handle_event(event_id, value)) return 0; event = kzalloc(size, GFP_ATOMIC); if (!event) return -ENOMEM; event->header.id = avm_event_id_led_status; event->led = event_id; event->state = value; return avm_event_source_trigger(event_source, avm_event_id_led_status, size, event); } static void brightness_cb(struct event *event, int old, int new) { if (new > 100) new = 100; hui_kobj->brightness = new; } static void ab_cb(struct event *event, int old, int new) { int val = event_get_value(ab); if (!new) return; atomic_set(&event->current_value, 0); atomic_set(&event->pending_value, 0); if (event == ab_onhook || event == ab_fehler) val &= ~new; else val |= new; if (val != event_get_value(ab)) { atomic_set(&ab->current_value, val); atomic_set(&ab->pending_value, val); hui_log_event(ab, val); } } void events_init(void) { struct _avm_event_id_mask id_mask; add_simple_proc_file("avm/hui/event_state", NULL, proc_event_state, NULL); avm_event_build_id_mask(&id_mask, 1, avm_event_id_led_status); event_sink = avm_event_sink_register("hui", &id_mask, event_handler, NULL); event_source = avm_event_source_register("hui", &id_mask, NULL, NULL); #if IS_ENABLED(CONFIG_AVM_LED_EVENTS) led_event_action = _avm_hui_send_event; #endif event_set_callback(dim_brightness, brightness_cb); event_set_callback(ab_onhook, ab_cb); ab_onhook->flags |= EVENT_FLAG_TRANSIENT; event_set_callback(ab_fehler, ab_cb); ab_fehler->flags |= EVENT_FLAG_TRANSIENT; event_set_callback(ab_offhook, ab_cb); ab_offhook->flags |= EVENT_FLAG_TRANSIENT; event_set_callback(ab_active, ab_cb); ab_active->flags |= EVENT_FLAG_TRANSIENT; } void events_exit(void) { #if IS_ENABLED(CONFIG_AVM_LED_EVENTS) led_event_action = NULL; #endif if (event_sink) avm_event_sink_release(event_sink); if (event_source) avm_event_source_release(event_source); } const char *avm_hui_get_event_name(enum _led_event event_id) { int i; const struct avm_hui_event_info *info; foreach_avm_hui_event(i, info) { if (info->id == event_id) return info->name; } return NULL; } const struct avm_hui_event_info *avm_hui_get_info_by_id(enum _led_event id) { int i; const struct avm_hui_event_info *info; foreach_avm_hui_event(i, info) { if (info->id == id) return info; } return NULL; } const struct avm_hui_event_info *avm_hui_get_info_by_name(const char *name) { int i; const struct avm_hui_event_info *info; foreach_avm_hui_event(i, info) { if (strcmp(info->name, name) == 0) return info; } return NULL; }