/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018 Andriy Gapon * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _SYS_WFHANDLER_H_ #define _SYS_WFHANDLER_H_ #include #include #include typedef void wfhandler_cb(void *); /* XXX */ struct wfhandler_entry { SLIST_ENTRY(wfhandler_entry) wfe_link; wfhandler_cb *wfe_func; void *wfe_arg; int wfe_priority; }; struct wfhandler_list { struct mtx wfl_lock; SLIST_HEAD(, wfhandler_entry) wfl_entries; volatile int wfl_phase; volatile int wfl_active[2]; }; /* Generic priority levels */ #define WFHANDLER_PRI_FIRST 0 #define WFHANDLER_PRI_ANY 10000 #define WFHANDLER_PRI_LAST 20000 void wfhandler_list_init(const char *name, struct wfhandler_list *list) { mtx_init(&list->wfl_lock, name, NULL, MTX_DEF); SLIST_INIT(&list->wfl_entries); list->wfl_phase = 0; list->wfl_active[0] = 0; list->wfl_active[1] = 0; } void wfhandler_register(struct wfhandler_list *list, struct wfhandler_entry *entry) { struct wfhandler_entry *elm; struct wfhandler_entry **prevptr; /* Serialize modifications. */ mtx_lock(&list->wfl_lock); LIST_FOREACH_PREVPTR(elm, prevptr, &list->wfl_entries, wfe_link) { if (elm->wfe_priority > entry->wfe_priority) break; } /* * A replacement for SLIST_INSERT_AFTER that ensures that the new * element is linked into the list after its next pointer has been * setup. Also, the previous element is not required. * prevptr, a pointer to the previous element's or the list head's * pointer to the next element, is sufficient. */ SLIST_NEXT(entry, wfe_link) = elm; atomic_store_rel_ptr(prevptr, entry); mtx_unlock(&list->wfl_lock); } void wfhandler_deregister(struct wfhandler_list *list, struct wfhandler_entry *entry) { struct wfhandler_entry *elm; struct wfhandler_entry **prevptr; int phase; /* Serialize modifications. */ mtx_lock(&list->wfl_lock); phase = list->wfl_phase; SLIST_FOREACH_PREVPTR(elm, prevptr, &list->wfl_entries, wfe_link) { if (elm == entry) { /* * Replacement for SLIST_REMOVE_AFTER and * SLIST_REMOVE_HEAD that does not trash the next * pointer in the removed element. It also can use * prevptr instead of the previous element or the list * head. */ *prevptr = SLIST_NEXT(entry, wfe_link); break; } } KASSERT(elm != NULL, ("entry %p is not on handler list")); /* Switch new readers to the other busy counter. */ KASSERT(&list->wfl_active[!phase] == 0, ("idle generation is busy")); atomic_store_rel_int(&list->wfl_phase, !phase); /* * Make sure that the list and generation updates are not reordered * with the check. */ atomic_thread_fence_seq_cst(); /* * Make sure that later operations are not reordered before the check. */ while (atomic_load_acq_int(&list->wfl_active[gen]) > 0) cpu_pause(); mtx_unlock(&list->wfl_lock); /* This code pokes into sys/queue.h internals. */ TRASHIT(entry->wfe_link.sle_next); } void wfhandler_invoke(struct wfhandler_list *list) { struct wfhandler_entry *elm; int phase; /* * Ensure that all loads are after the busy count is updated * for the current generation. */ phase = list->wfl_phase; atomic_add_acq_int(&list->wfl_active[phase], 1); SLIST_FOREACH(elm, &list->wfl_entries, wfe_link) elm->wfe_func(elm->wfe_arg); /* Ensure that all loads are before the flag is reset. */ atomic_add_rel_int(&list->wfl_active[phase], -1); } #endif /* _SYS_WFHANDLER_H_ */