Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segfault of as_zmk_battery_state_changed on native_posix_64 #2792

Open
mlouielu opened this issue Jan 24, 2025 · 1 comment · May be fixed by #2793
Open

Segfault of as_zmk_battery_state_changed on native_posix_64 #2792

mlouielu opened this issue Jan 24, 2025 · 1 comment · May be fixed by #2793

Comments

@mlouielu
Copy link

At the following config, when building on native_posix_64, it will segfault during init process:

CONFIG_ZMK_DISPLAY=y
CONFIG_ZMK_BLE=y
CONFIG_ZMK_BATTERY_REPORTING=y

GDB backtrace:

[00:00:00.000,000] <dbg> zmk: initialize_display: 

Thread 6 "zmk.elf" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff73a66c0 (LWP 1280968)]
0x0000000000414315 in as_zmk_battery_state_changed (eh=eh@entry=0x0)
    at /zmk/app/src/events/battery_state_changed.c:10
10	ZMK_EVENT_IMPL(zmk_battery_state_changed);
(gdb) bt
#0  0x0000000000414315 in as_zmk_battery_state_changed (eh=eh@entry=0x0)
    at /zmk/app/src/events/battery_state_changed.c:10
#1  0x0000000000414d3c in battery_status_get_state (eh=eh@entry=0x0)
    at /zmk/app/src/display/widgets/battery_status.c:66
#2  0x0000000000414d77 in widget_battery_status_refresh_state (eh=eh@entry=0x0)
    at /zmk/app/src/display/widgets/battery_status.c:76
#3  0x0000000000414e23 in widget_battery_status_init ()
    at /zmk/app/src/display/widgets/battery_status.c:76
#4  0x0000000000414e69 in zmk_widget_battery_status_init (
    widget=widget@entry=0x4d01c0 <battery_status_widget>, 
    parent=parent@entry=0x4d0db8 <lvgl_heap_mem+2000>)
    at /zmk/app/src/display/widgets/battery_status.c:89
#5  0x0000000000414b7a in zmk_display_status_screen ()
    at /zmk/app/src/display/status_screen.c:42
#6  0x0000000000414a57 in initialize_display (work=work@entry=0x456500 <init_work>)
    at /zmk/app/src/display/main.c:116
#7  0x0000000000445a44 in work_queue_main (workq_ptr=0x4d00a0 <k_sys_work_q>, 
    workq_ptr@entry=<error reading variable: value has been optimized out>, 
    p2=<error reading variable: value has been optimized out>, 
    p3=<error reading variable: value has been optimized out>)
    at /zmk/zephyr/kernel/work.c:672
#8  0x0000000000415705 in z_thread_entry (entry=<optimized out>, p1=<optimized out>, 
    p2=<optimized out>, p3=<optimized out>)
    at /zmk/zephyr/lib/os/thread_entry.c:48
#9  0x00000000004194d1 in posix_arch_thread_entry (
    pa_thread_status=0x4e0f78 <sys_work_q_stack+2008>)
    at /zmk/zephyr/arch/posix/core/thread.c:56
#10 0x0000000000419748 in posix_thread_starter (arg=0x3)
    at /zmk/zephyr/arch/posix/core/posix_core.c:293
#11 0x00007ffff7b5639d in ?? () from /usr/lib/libc.so.6
#12 0x00007ffff7bdb49c in ?? () from /usr/lib/libc.so.6

Cause by the event_manager macro #define ZMK_EVENT_IMPL(event_type), in struct event_type *as_##event_type(const zmk_event_t *eh) which doesn't check the eh pointer is NULL or not:

    struct event_type *as_##event_type(const zmk_event_t *eh) {                                    \
        return (eh->event == &zmk_event_##event_type) ? &((struct event_type##_event *)eh)->data   \
                                                      : NULL;                                      \
    };
mlouielu added a commit to mlouielu/zmk that referenced this issue Jan 24, 2025
@mlouielu mlouielu linked a pull request Jan 24, 2025 that will close this issue
5 tasks
@joelspadin
Copy link
Collaborator

It feels weird to me that the <widget>_init() function defined by ZMK_DISPLAY_WIDGET_LISTENER passes null to the get state function, which expects an event pointer. As far as I can tell, this is the only place in ZMK where a zmk_event_t* parameter may be null. While making the event type check function support being given null does fix it in this case, it think that is just hiding a bigger design issue with the display code.

The battery status widget is the only widget that actually uses the event parameter. If it is a battery event, it reads from the event. If it is some other type of event, it still has to populate the entire state struct though, and it can't get it from the event, so it has to read it from elsewhere.

The rest of the widgets just ignore the event entirely. Why even pass the event if we're mostly not going to use it?

Some alternative designs to consider:

  1. Don't pass the event to the <widget>_get_state() function at all. Update battery_status_get_state() to always read from zmk_battery_state_of_charge().
    • Then there is no difference between initialization and handling a state change.
    • This is potentially less efficient though, because the data we need to copy to the state variable is probably already in the event data, and functions like zmk_ble_active_profile_is_connected() do a lot more work than just returning the value of a static variable.
    • It also requires updating the entire state even if only a part of it changed.
  2. Separate <widget>_get_state() into init and update functions.
    • The init function takes no parameters. It is called once on startup, and it should populate the entire state struct.
    • The update function takes a pointer to the current state and an event pointer. It is called whenever the widget listener handles an event. It should only update the parts of the state struct that are relevant to the event.
    • The update function could potentially run directly on the display thread, provided it can always get the data it needs from events. If all the init functions are called prior to starting the display thread, then it would also be safe to remove the mutex around each state variable.
    • This design feels cleaner to me, but it also requires more code.
// Option 1
static struct foo_state foo_get_state(void) {
    // Regardless of which of a or b changed (or neither on startup), we must populate every field
    return (struct foo_state) {
        .a = get_current_a(),
        .b = get_current_b(),
    };
}

ZMK_DISPLAY_WIDGET_LISTENER(widget_foo, struct foo_state, foo_update_cb, foo_get_state);
ZMK_SUBSCRIPTION(widget_foo, a_changed);
ZMK_SUBSCRIPTION(widget_foo, b_changed);

// Option 2
static struct foo_state foo_init_state(void) {
    // This is called once on startup and has to populate every field
    return (struct foo_state) {
        .a = get_current_a(),
        .b = get_current_b(),
    };
}

static void foo_update_state(struct foo_state* state, const zmk_event_t* eh) {
    const struct a_changed* a;
    const struct b_changed* b;

    // Only update the fields that changed using the event data instead of calling other functions.
    if ((a = as_a_changed(eh)) != NULL) {
        state->a = a->value;
    } else if ((b = as_b_changed(eh)) != NULL) {
        state->b = b->value;
    }
}

ZMK_DISPLAY_WIDGET_LISTENER(widget_foo, struct foo_state, foo_update_cb, foo_init_state, foo_update_state);
ZMK_SUBSCRIPTION(widget_foo, a_changed);
ZMK_SUBSCRIPTION(widget_foo, b_changed);

@petejohanson what are your thoughts on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants