Commit 8c687c9e authored by Philippe Gerum's avatar Philippe Gerum
Browse files

evl: introduce the observable element



Observables enable the observer design pattern, in which any number of
observer threads can be notified of updates to any number of
observable subjects, in a loosely coupled fashion. In the same move,
an EVL thread becomes in and of itself an observable which can be
monitored for events.

As a by-product, provide support for channeling SIGDEBUG notifications
to threads through their own observable on request instead of, or in
addition to issuing SIGDEBUG. The built-in runtime error detection
capabilities the core provides can feed health monitors, based on the
observability of threads.

The core switches to ABI 23 as a result of these changes.
Signed-off-by: Philippe Gerum's avatarPhilippe Gerum <rpm@xenomai.org>
parent 003dba49
......@@ -5,9 +5,11 @@
#ifdef CONFIG_EVL
struct evl_thread;
struct evl_subscriber;
struct oob_thread_state {
struct evl_thread *thread;
struct evl_subscriber *subscriber;
int preempt_count;
};
......@@ -15,6 +17,7 @@ static inline
void evl_init_thread_state(struct oob_thread_state *p)
{
p->thread = NULL;
p->subscriber = NULL;
p->preempt_count = 0;
}
......
......@@ -38,6 +38,12 @@ struct evl_element;
#define EVL_DEVHASH_BITS 8
struct evl_index {
struct rb_root root;
hard_spinlock_t lock;
fundle_t generator;
};
struct evl_factory {
const char *name;
const struct file_operations *fops;
......@@ -59,11 +65,7 @@ struct evl_factory {
kuid_t kuid;
kgid_t kgid;
unsigned long *minor_map;
struct evl_index {
struct rb_root root;
hard_spinlock_t lock;
fundle_t generator;
} index;
struct evl_index index;
DECLARE_HASHTABLE(name_hash, EVL_DEVHASH_BITS);
struct mutex hash_lock;
}; /* Internal. */
......@@ -109,16 +111,22 @@ void evl_destroy_element(struct evl_element *e);
void evl_get_element(struct evl_element *e);
struct evl_element *
__evl_get_element_by_fundle(struct evl_factory *fac,
__evl_get_element_by_fundle(struct evl_index *map,
fundle_t fundle);
#define evl_get_element_by_fundle(__fac, __fundle, __type) \
#define evl_get_element_by_fundle(__map, __fundle, __type) \
({ \
struct evl_element *__e; \
__e = __evl_get_element_by_fundle(__fac, __fundle); \
__e = __evl_get_element_by_fundle(__map, __fundle); \
__e ? container_of(__e, __type, element) : NULL; \
})
#define evl_get_factory_element_by_fundle(__fac, __fundle, __type) \
({ \
struct evl_index *__map = &(__fac)->index; \
evl_get_element_by_fundle(__map, __fundle, __type); \
})
/*
* An element can be disposed of only after the device backing it is
* removed. If @dev is valid, so is @e at the time of the call.
......@@ -135,9 +143,14 @@ static inline bool evl_element_is_public(struct evl_element *e)
return !!(e->clone_flags & EVL_CLONE_PUBLIC);
}
static inline bool evl_element_is_core(struct evl_element *e)
static inline bool evl_element_has_coredev(struct evl_element *e)
{
return !!(e->clone_flags & EVL_CLONE_COREDEV);
}
static inline bool evl_element_is_observable(struct evl_element *e)
{
return !!(e->clone_flags & EVL_CLONE_CORE);
return !!(e->clone_flags & EVL_CLONE_OBSERVABLE);
}
void evl_put_element(struct evl_element *e);
......@@ -154,12 +167,21 @@ int evl_create_core_element_device(struct evl_element *e,
void evl_remove_element_device(struct evl_element *e);
void evl_index_element(struct evl_element *e);
void evl_index_element(struct evl_index *map,
struct evl_element *e);
int evl_index_element_at(struct evl_element *e,
fundle_t fundle);
static inline void evl_index_factory_element(struct evl_element *e)
{
evl_index_element(&e->factory->index, e);
}
void evl_unindex_element(struct evl_element *e);
void evl_unindex_element(struct evl_index *map,
struct evl_element *e);
static inline void evl_unindex_factory_element(struct evl_element *e)
{
evl_unindex_element(&e->factory->index, e);
}
int evl_early_init_factories(void);
......@@ -177,5 +199,6 @@ extern struct evl_factory evl_thread_factory;
extern struct evl_factory evl_trace_factory;
extern struct evl_factory evl_xbuf_factory;
extern struct evl_factory evl_proxy_factory;
extern struct evl_factory evl_observable_factory;
#endif /* !_EVL_FACTORY_H */
......@@ -13,7 +13,7 @@
#include <evl/list.h>
#include <evl/assert.h>
#include <evl/timer.h>
#include <evl/thread.h>
#include <evl/wait.h>
#include <uapi/evl/mutex.h>
struct evl_clock;
......@@ -79,7 +79,7 @@ void evl_flush_mutex(struct evl_mutex *mutex,
void evl_commit_mutex_ceiling(struct evl_mutex *mutex);
void evl_detect_boost_drop(struct evl_thread *owner);
void evl_detect_boost_drop(void);
int evl_reorder_mutex_wait(struct evl_thread *waiter,
struct evl_thread *originator);
......
/*
* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2020 Philippe Gerum <rpm@xenomai.org>
*/
#ifndef _EVL_OBSERVABLE_H
#define _EVL_OBSERVABLE_H
#include <linux/types.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <evl/factory.h>
#include <evl/wait.h>
#include <evl/poll.h>
#include <uapi/evl/observable.h>
struct file;
struct evl_observable {
struct evl_element element;
struct list_head observers; /* struct evl_observer */
struct list_head flush_list; /* struct evl_observer */
struct evl_wait_queue oob_wait;
wait_queue_head_t inband_wait;
struct evl_poll_head poll_head;
struct irq_work wake_irqwork;
struct irq_work flush_irqwork;
evl_spinlock_t lock; /* guards observers and flush_list */
u32 serial_counter;
int writable_observers;
};
void evl_drop_subscriptions(struct evl_subscriber *subscriber);
struct evl_observable *evl_alloc_observable(int clone_flags);
void evl_flush_observable(struct evl_observable *observable);
bool evl_send_observable(struct evl_observable *observable, int tag,
union evl_value details);
ssize_t evl_read_observable(struct evl_observable *observable,
char __user *u_buf, size_t count, bool wait);
ssize_t evl_write_observable(struct evl_observable *observable,
const char __user *u_buf, size_t count);
__poll_t evl_oob_poll_observable(struct evl_observable *observable,
struct oob_poll_wait *wait);
__poll_t evl_poll_observable(struct evl_observable *observable,
struct file *filp, poll_table *pt);
long evl_ioctl_observable(struct evl_observable *observable,
unsigned int cmd, unsigned long arg);
#endif /* !_EVL_OBSERVABLE_H */
......@@ -41,6 +41,7 @@ struct evl_poll_watchpoint {
unsigned int fd;
int events_polled;
int events_received;
union evl_value pollval;
struct oob_poll_wait wait;
struct evl_flag *flag;
struct file *filp;
......
......@@ -31,35 +31,32 @@
#define EVL_THREAD_BLOCK_BITS (T_SUSP|T_PEND|T_DELAY|T_WAIT|T_DORMANT|T_INBAND|T_HALT|T_PTSYNC)
/* Information bits an EVL thread may receive from a blocking op. */
#define EVL_THREAD_INFO_MASK (T_RMID|T_TIMEO|T_BREAK|T_WAKEN|T_ROBBED|T_KICKED|T_BCAST)
/* Mode bits configurable via EVL_THRIOC_SET/CLEAR_MODE. */
#define EVL_THREAD_MODE_BITS (T_WOSS|T_WOLI|T_WOSX|T_HMSIG|T_HMOBS)
/*
* These are special internal values of SIGDEBUG causes which are
* never sent to user-space, but specifically handled by
* evl_switch_inband().
* These are special internal values of HM diags which are never sent
* to user-space, but specifically handled by evl_switch_inband().
*/
#define SIGDEBUG_NONE 0
#define SIGDEBUG_TRAP -1
#define EVL_HMDIAG_NONE 0
#define EVL_HMDIAG_TRAP -1
struct evl_thread;
struct evl_rq;
struct evl_sched_class;
struct evl_poll_watchpoint;
struct evl_wait_channel;
struct evl_observable;
struct file;
struct evl_init_thread_attr {
const struct cpumask *affinity;
struct evl_observable *observable;
int flags;
struct evl_sched_class *sched_class;
union evl_sched_param sched_param;
};
struct evl_wait_channel {
int (*reorder_wait)(struct evl_thread *waiter,
struct evl_thread *originator);
int (*follow_depend)(struct evl_wait_channel *wchan,
struct evl_thread *originator);
struct list_head wait_list;
};
struct evl_thread {
evl_spinlock_t lock;
......@@ -154,8 +151,9 @@ struct evl_thread {
struct completion exited;
kernel_cap_t raised_cap;
struct list_head kill_next;
struct oob_mm_state *oob_mm; /* Mostly RO. */
struct list_head ptsync_next; /* covered by oob_mm->lock. */
struct oob_mm_state *oob_mm; /* Mostly RO. */
struct list_head ptsync_next; /* covered by oob_mm->lock. */
struct evl_observable *observable;
char *name;
};
......@@ -237,6 +235,16 @@ static inline void evl_test_cancel(void)
__evl_test_cancel(curr);
}
static inline struct evl_subscriber *evl_get_subscriber(void)
{
return dovetail_current_state()->subscriber;
}
static inline void evl_set_subscriber(struct evl_subscriber *sbr)
{
dovetail_current_state()->subscriber = sbr;
}
ktime_t evl_get_thread_timeout(struct evl_thread *thread);
ktime_t evl_get_thread_period(struct evl_thread *thread);
......@@ -283,7 +291,10 @@ int evl_wait_thread_period(unsigned long *overruns_r);
void evl_cancel_thread(struct evl_thread *thread);
int evl_join_thread(struct evl_thread *thread,
bool uninterruptible);
bool uninterruptible);
void evl_notify_thread(struct evl_thread *thread,
int tag, union evl_value details);
void evl_get_thread_state(struct evl_thread *thread,
struct evl_thread_state *statebuf);
......@@ -308,6 +319,8 @@ int evl_set_thread_schedparam(struct evl_thread *thread,
int evl_killall(int mask);
bool evl_is_thread_file(struct file *filp);
void __evl_propagate_schedparam_change(struct evl_thread *curr);
static inline void evl_propagate_schedparam_change(struct evl_thread *curr)
......@@ -325,6 +338,7 @@ int __evl_run_kthread(struct evl_kthread *kthread, int clone_flags);
struct evl_init_thread_attr __iattr = { \
.flags = 0, \
.affinity = __affinity, \
.observable = NULL, \
.sched_class = &evl_sched_fifo, \
.sched_param.fifo.prio = __priority, \
}; \
......
......@@ -11,17 +11,24 @@
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <evl/lock.h>
#include <evl/list.h>
#include <evl/timer.h>
#include <evl/clock.h>
#include <evl/thread.h>
#include <evl/sched.h>
#include <trace/events/evl.h>
#include <uapi/evl/thread.h>
#define EVL_WAIT_FIFO 0
#define EVL_WAIT_PRIO BIT(0)
struct evl_wait_channel {
int (*reorder_wait)(struct evl_thread *waiter,
struct evl_thread *originator);
int (*follow_depend)(struct evl_wait_channel *wchan,
struct evl_thread *originator);
struct list_head wait_list;
};
struct evl_wait_queue {
int flags;
struct evl_clock *clock;
......@@ -90,14 +97,6 @@ static inline bool evl_wait_active(struct evl_wait_queue *wq)
return !list_empty(&wq->wchan.wait_list);
}
static inline
struct evl_thread *evl_wait_head(struct evl_wait_queue *wq)
{
assert_evl_lock(&wq->lock);
return list_first_entry_or_null(&wq->wchan.wait_list,
struct evl_thread, wait_next);
}
void __evl_init_wait(struct evl_wait_queue *wq,
struct evl_clock *clock,
int flags,
......@@ -112,6 +111,8 @@ void __evl_init_wait(struct evl_wait_queue *wq,
void evl_destroy_wait(struct evl_wait_queue *wq);
struct evl_thread *evl_wait_head(struct evl_wait_queue *wq);
void evl_flush_wait_locked(struct evl_wait_queue *wq,
int reason);
......
......@@ -293,7 +293,7 @@ TRACE_EVENT(evl_init_thread,
TP_fast_assign(
__entry->thread = thread;
__assign_str(thread_name, thread->name);
__entry->flags = iattr->flags;
__entry->flags = iattr->flags | (iattr->observable ? T_OBSERV : 0);
__assign_str(class_name, iattr->sched_class->name);
__entry->cprio = thread->cprio;
__entry->status = status;
......@@ -497,18 +497,18 @@ DEFINE_EVENT(curr_thread_event, evl_switched_oob,
TP_ARGS(curr)
);
#define evl_print_switch_cause(cause) \
__print_symbolic(cause, \
{ SIGDEBUG_TRAP, "breakpoint trap" }, \
{ SIGDEBUG_NONE, "undefined" }, \
{ SIGDEBUG_MIGRATE_SIGNAL, "signal" }, \
{ SIGDEBUG_MIGRATE_SYSCALL, "syscall" }, \
{ SIGDEBUG_MIGRATE_FAULT, "fault" }, \
{ SIGDEBUG_MIGRATE_PRIOINV, "priority inversion" }, \
{ SIGDEBUG_WATCHDOG, "watchdog" }, \
{ SIGDEBUG_MUTEX_IMBALANCE, "mutex imbalance" }, \
{ SIGDEBUG_MUTEX_SLEEP, "mutex sleep" }, \
{ SIGDEBUG_STAGE_LOCKED, "stage exclusion" } )
#define evl_print_switch_cause(cause) \
__print_symbolic(cause, \
{ EVL_HMDIAG_TRAP, "breakpoint trap" }, \
{ EVL_HMDIAG_NONE, "undefined" }, \
{ EVL_HMDIAG_SIGDEMOTE, "in-band signal" }, \
{ EVL_HMDIAG_SYSDEMOTE, "in-band syscall" }, \
{ EVL_HMDIAG_EXDEMOTE, "processor exception" },\
{ EVL_HMDIAG_WATCHDOG, "watchdog" }, \
{ EVL_HMDIAG_LKDEPEND, "lock dependency" }, \
{ EVL_HMDIAG_LKIMBALANCE, "lock imbalance" }, \
{ EVL_HMDIAG_LKSLEEP, "sleep holding lock" }, \
{ EVL_HMDIAG_STAGEX, "stage exclusion" } )
TRACE_EVENT(evl_switch_inband,
TP_PROTO(int cause),
......@@ -579,23 +579,26 @@ TRACE_EVENT(evl_inband_wakeup,
);
TRACE_EVENT(evl_inband_signal,
TP_PROTO(struct task_struct *task, int sig),
TP_ARGS(task, sig),
TP_PROTO(struct evl_thread *thread, int sig, int sigval),
TP_ARGS(thread, sig, sigval),
TP_STRUCT__entry(
__field(pid_t, pid)
__array(char, comm, TASK_COMM_LEN)
__field(struct evl_thread *, thread)
__field(int, sig)
__field(int, sigval)
),
TP_fast_assign(
__entry->pid = task_pid_nr(task);
__entry->thread = thread;
__entry->sig = sig;
memcpy(__entry->comm, task->comm, TASK_COMM_LEN);
__entry->sigval = sigval;
),
TP_printk("pid=%d comm=%s sig=%d",
__entry->pid, __entry->comm, __entry->sig)
/* Caller holds a reference on @thread, memory cannot be stale. */
TP_printk("thread=%s pid=%d sig=%d sigval=%d",
evl_element_name(&__entry->thread->element),
evl_get_inband_pid(__entry->thread),
__entry->sig, __entry->sigval)
);
DEFINE_EVENT(timer_event, evl_timer_stop,
......
......@@ -11,14 +11,14 @@
#include <uapi/evl/sched.h>
/* Earliest ABI level we support. */
#define EVL_ABI_BASE 21
#define EVL_ABI_BASE 23
/*
* Current/latest ABI level we support. We may decouple the base and
* current ABI levels by providing backward compatibility from the
* latter to the former. CAUTION: a litteral value is required for the
* current ABI definition (scripts reading this may be naive).
*/
#define EVL_ABI_LEVEL 22
#define EVL_ABI_LEVEL 23
#define EVL_CONTROL_DEV "/dev/evl/control"
......
......@@ -17,10 +17,14 @@ struct evl_element_ids {
__u32 state_offset;
};
#define EVL_CLONE_PUBLIC (1 << 16)
#define EVL_CLONE_PRIVATE (0 << 16)
#define EVL_CLONE_CORE (1 << 31)
#define EVL_CLONE_MASK ((__u32)-1 << 16)
/* The core only uses bits 16-31, rest is available to libevl. */
#define EVL_CLONE_PUBLIC (1 << 16)
#define EVL_CLONE_PRIVATE (0 << 16)
#define EVL_CLONE_OBSERVABLE (1 << 17)
#define EVL_CLONE_NONBLOCK (1 << 18)
#define EVL_CLONE_MASTER (1 << 19)
#define EVL_CLONE_COREDEV (1 << 31)
#define EVL_CLONE_MASK (((__u32)-1 << 16) & ~EVL_CLONE_COREDEV)
struct evl_clone_req {
__u64 name_ptr; /* (const char *name) */
......
/*
* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
*
* Copyright (C) 2020 Philippe Gerum <rpm@xenomai.org>
*/
#ifndef _EVL_UAPI_OBSERVABLE_H
#define _EVL_UAPI_OBSERVABLE_H
#include <uapi/evl/types.h>
#define EVL_OBSERVABLE_DEV "observable"
/* __evl_notification.flags */
#define EVL_NOTIFY_ALWAYS (0 << 0)
#define EVL_NOTIFY_ONCHANGE (1 << 0)
#define EVL_NOTIFY_MASK EVL_NOTIFY_ONCHANGE
struct evl_notice {
__u32 tag;
union evl_value event;
};
/* Notice tags below this value are reserved to the core. */
#define EVL_NOTICE_USER 64
struct evl_subscription {
__u32 backlog_count;
__u32 flags;
};
struct __evl_notification {
__u32 tag;
__u32 serial;
__s32 issuer;
union evl_value event;
struct __evl_timespec date;
};
#define EVL_OBSERVABLE_IOCBASE 'o'
#define EVL_OBSIOC_SUBSCRIBE _IOW(EVL_OBSERVABLE_IOCBASE, 0, struct evl_subscription)
#define EVL_OBSIOC_UNSUBSCRIBE _IO(EVL_OBSERVABLE_IOCBASE, 1)
#endif /* !_EVL_UAPI_OBSERVABLE_H */
......@@ -21,11 +21,13 @@ struct evl_poll_ctlreq {
__u32 action;
__u32 fd;
__u32 events;
union evl_value pollval;
};
struct evl_poll_event {
__u32 fd;
__u32 events;
union evl_value pollval;
};
struct evl_poll_waitreq {
......
......@@ -9,6 +9,9 @@
#ifndef _EVL_UAPI_SIGNAL_H
#define _EVL_UAPI_SIGNAL_H
/*
* EVL_HMDIAG_xxx codes are possible values of sigdebug_cause().
*/
#define SIGDEBUG SIGXCPU
#define sigdebug_code(si) ((si)->si_value.sival_int)
#define sigdebug_cause(si) (sigdebug_code(si) & 0xff)
......@@ -16,14 +19,4 @@
#define sigdebug_marked(si) \
((sigdebug_code(si) & 0xffff0000) == sigdebug_marker)
/* Possible values of sigdebug_cause() */
#define SIGDEBUG_MIGRATE_SIGNAL 1
#define SIGDEBUG_MIGRATE_SYSCALL 2
#define SIGDEBUG_MIGRATE_FAULT 3
#define SIGDEBUG_MIGRATE_PRIOINV 4
#define SIGDEBUG_WATCHDOG 5
#define SIGDEBUG_MUTEX_IMBALANCE 6
#define SIGDEBUG_MUTEX_SLEEP 7
#define SIGDEBUG_STAGE_LOCKED 8
#endif /* !_EVL_UAPI_SIGNAL_H */
......@@ -30,10 +30,13 @@
#define T_ROOT 0x00001000 /* Root thread (in-band kernel placeholder) */
#define T_WEAK 0x00002000 /* Weak scheduling (in-band) */
#define T_USER 0x00004000 /* Userland thread */
#define T_WOSS 0x00008000 /* Warn on stage switch (SIGDEBUG) */
#define T_WOLI 0x00010000 /* Warn on locking inconsistency (SIGDEBUG) */
#define T_WOSX 0x00020000 /* Warn on stage exclusion (SIGDEBUG) */
#define T_WOSS 0x00008000 /* Warn on stage switch (HM) */
#define T_WOLI 0x00010000 /* Warn on locking inconsistency (HM) */
#define T_WOSX 0x00020000 /* Warn on stage exclusion (HM) */
#define T_PTRACE 0x00040000 /* Stopped on ptrace event */
#define T_OBSERV 0x00080000 /* Observable (only for export to userland) */
#define T_HMSIG 0x00100000 /* Notify HM events via SIGDEBUG */
#define T_HMOBS 0x00200000 /* Notify HM events via observable */
/* Information flags (shared) */
......@@ -44,11 +47,11 @@
#define T_WAKEN 0x00000010 /* Thread waken up upon resource availability */
#define T_ROBBED 0x00000020 /* Robbed from resource ownership */
#define T_CANCELD 0x00000040 /* Cancellation request is pending */
#define T_PIALERT 0x00000080 /* Priority inversion alert (SIGDEBUG sent) */
#define T_PIALERT 0x00000080 /* Priority inversion alert (HM notified) */
#define T_SCHEDP 0x00000100 /* Schedparam propagation is pending */
#define T_BCAST 0x00000200 /* Woken up upon resource broadcast */
#define T_SIGNAL 0x00000400 /* Event monitor signaled */
#define T_SXALERT 0x00000800 /* Stage exclusion alert (SIGDEBUG sent) */
#define T_SXALERT 0x00000800 /* Stage exclusion alert (HM notified) */
#define T_PTSIG 0x00001000 /* Ptrace signal is pending */
#define T_PTSTOP 0x00002000 /* Ptrace stop is ongoing */
#define T_PTJOIN 0x00004000 /* Ptracee should join ptsync barrier */
......@@ -75,10 +78,21 @@
* 'b' -> Priority boost undergoing
* '#' -> Ptrace sync ongoing
* 'r' -> Undergoes round-robin
* 't' -> Warned on stage switch (T_WOSS -> SIGDEBUG)
* 't' -> Warned on stage switch (T_WOSS)
* 'T' -> Stopped on ptrace event
* 'o' -> Observable
*/
#define EVL_THREAD_STATE_LABELS "SWDpRUZXHb#r...t..T"
#define EVL_THREAD_STATE_LABELS "SWDpRUZXHb#r...t..To.."
/* Health monitoring diag codes (via observable or SIGDEBUG). */
#define EVL_HMDIAG_SIGDEMOTE 1
#define EVL_HMDIAG_SYSDEMOTE 2
#define EVL_HMDIAG_EXDEMOTE 3
#define EVL_HMDIAG_WATCHDOG 4
#define EVL_HMDIAG_LKDEPEND 5
#define EVL_HMDIAG_LKIMBALANCE 6
#define EVL_HMDIAG_LKSLEEP 7
#define EVL_HMDIAG_STAGEX 8
struct evl_user_window {
__u32 state;
......
......@@ -44,4 +44,14 @@ struct __evl_itimerspec {
struct __evl_timespec it_value;
};
union evl_value {
__s32 val;
__s64 lval;
void *ptr;
};
#define evl_intval(__val) ((union evl_value){ .lval = (__val) })
#define evl_ptrval(__ptr) ((union evl_value){ .ptr = (__ptr) })
#define evl_nil evl_intval(0)
#endif /* !_EVL_UAPI_TYPES_H */
......@@ -103,6 +103,21 @@ config EVL_NR_PROXIES
be alive concurrently in the system for user-space
applications.