#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/hhook.h>
#include <sys/khelp.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/module_khelp.h>
#include <sys/osd.h>
#include <sys/queue.h>
#include <sys/refcount.h>
#include <sys/systm.h>
#include <net/vnet.h>
struct hhook {
hhook_func_t hhk_func;
struct helper *hhk_helper;
void *hhk_udata;
STAILQ_ENTRY(hhook) hhk_next;
};
static MALLOC_DEFINE(M_HHOOK, "hhook", "Helper hooks are linked off hhook_head lists");
LIST_HEAD(hhookheadhead, hhook_head);
struct hhookheadhead hhook_head_list;
VNET_DEFINE(struct hhookheadhead, hhook_vhead_list);
#define V_hhook_vhead_list VNET(hhook_vhead_list)
static struct mtx hhook_head_list_lock;
MTX_SYSINIT(hhookheadlistlock, &hhook_head_list_lock, "hhook_head list lock",
MTX_DEF);
static uint32_t n_hhookheads;
static void hhook_head_destroy(struct hhook_head *hhh);
void khelp_new_hhook_registered(struct hhook_head *hhh, uint32_t flags);
#define HHHLIST_LOCK() mtx_lock(&hhook_head_list_lock)
#define HHHLIST_UNLOCK() mtx_unlock(&hhook_head_list_lock)
#define HHHLIST_LOCK_ASSERT() mtx_assert(&hhook_head_list_lock, MA_OWNED)
#define HHH_LOCK_INIT(hhh) rm_init(&(hhh)->hhh_lock, "hhook_head rm lock")
#define HHH_LOCK_DESTROY(hhh) rm_destroy(&(hhh)->hhh_lock)
#define HHH_WLOCK(hhh) rm_wlock(&(hhh)->hhh_lock)
#define HHH_WUNLOCK(hhh) rm_wunlock(&(hhh)->hhh_lock)
#define HHH_RLOCK(hhh, rmpt) rm_rlock(&(hhh)->hhh_lock, (rmpt))
#define HHH_RUNLOCK(hhh, rmpt) rm_runlock(&(hhh)->hhh_lock, (rmpt))
void
hhook_run_hooks(struct hhook_head *hhh, void *ctx_data, struct osd *hosd)
{
struct hhook *hhk;
void *hdata;
struct rm_priotracker rmpt;
KASSERT(hhh->hhh_refcount > 0, ("hhook_head %p refcount is 0", hhh));
HHH_RLOCK(hhh, &rmpt);
STAILQ_FOREACH(hhk, &hhh->hhh_hooks, hhk_next) {
if (hhk->hhk_helper != NULL &&
hhk->hhk_helper->h_flags & HELPER_NEEDS_OSD) {
hdata = osd_get(OSD_KHELP, hosd, hhk->hhk_helper->h_id);
if (hdata == NULL)
continue;
} else
hdata = NULL;
hhk->hhk_func(hhh->hhh_type, hhh->hhh_id, hhk->hhk_udata,
ctx_data, hdata, hosd);
}
HHH_RUNLOCK(hhh, &rmpt);
}
int
hhook_add_hook(struct hhook_head *hhh, const struct hookinfo *hki, uint32_t flags)
{
struct hhook *hhk, *tmp;
int error;
error = 0;
if (hhh == NULL)
return (ENOENT);
hhk = malloc(sizeof(struct hhook), M_HHOOK,
M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
if (hhk == NULL)
return (ENOMEM);
hhk->hhk_helper = hki->hook_helper;
hhk->hhk_func = hki->hook_func;
hhk->hhk_udata = hki->hook_udata;
HHH_WLOCK(hhh);
STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
if (tmp->hhk_func == hki->hook_func &&
tmp->hhk_udata == hki->hook_udata) {
error = EEXIST;
break;
}
}
if (!error) {
STAILQ_INSERT_TAIL(&hhh->hhh_hooks, hhk, hhk_next);
hhh->hhh_nhooks++;
} else
free(hhk, M_HHOOK);
HHH_WUNLOCK(hhh);
return (error);
}
int
hhook_add_hook_lookup(const struct hookinfo *hki, uint32_t flags)
{
struct hhook_head **heads_to_hook, *hhh;
int error, i, n_heads_to_hook;
tryagain:
error = i = 0;
n_heads_to_hook = n_hhookheads;
heads_to_hook = malloc(n_heads_to_hook * sizeof(struct hhook_head *),
M_HHOOK, flags & HHOOK_WAITOK ? M_WAITOK : M_NOWAIT);
if (heads_to_hook == NULL)
return (ENOMEM);
HHHLIST_LOCK();
LIST_FOREACH(hhh, &hhook_head_list, hhh_next) {
if (hhh->hhh_type == hki->hook_type &&
hhh->hhh_id == hki->hook_id) {
if (i < n_heads_to_hook) {
heads_to_hook[i] = hhh;
refcount_acquire(&heads_to_hook[i]->hhh_refcount);
i++;
} else {
for (i--; i >= 0; i--)
refcount_release(&heads_to_hook[i]->hhh_refcount);
free(heads_to_hook, M_HHOOK);
HHHLIST_UNLOCK();
goto tryagain;
}
}
}
HHHLIST_UNLOCK();
for (i--; i >= 0; i--) {
if (!error)
error = hhook_add_hook(heads_to_hook[i], hki, flags);
refcount_release(&heads_to_hook[i]->hhh_refcount);
}
free(heads_to_hook, M_HHOOK);
return (error);
}
int
hhook_remove_hook(struct hhook_head *hhh, const struct hookinfo *hki)
{
struct hhook *tmp;
if (hhh == NULL)
return (ENOENT);
HHH_WLOCK(hhh);
STAILQ_FOREACH(tmp, &hhh->hhh_hooks, hhk_next) {
if (tmp->hhk_func == hki->hook_func &&
tmp->hhk_udata == hki->hook_udata) {
STAILQ_REMOVE(&hhh->hhh_hooks, tmp, hhook, hhk_next);
free(tmp, M_HHOOK);
hhh->hhh_nhooks--;
break;
}
}
HHH_WUNLOCK(hhh);
return (0);
}
int
hhook_remove_hook_lookup(const struct hookinfo *hki)
{
struct hhook_head *hhh;
HHHLIST_LOCK();
LIST_FOREACH(hhh, &hhook_head_list, hhh_next) {
if (hhh->hhh_type == hki->hook_type &&
hhh->hhh_id == hki->hook_id)
hhook_remove_hook(hhh, hki);
}
HHHLIST_UNLOCK();
return (0);
}
int
hhook_head_register(int32_t hhook_type, int32_t hhook_id, struct hhook_head **hhh,
uint32_t flags)
{
struct hhook_head *tmphhh;
tmphhh = hhook_head_get(hhook_type, hhook_id);
if (tmphhh != NULL) {
hhook_head_release(tmphhh);
return (EEXIST);
}
tmphhh = malloc(sizeof(struct hhook_head), M_HHOOK,
M_ZERO | ((flags & HHOOK_WAITOK) ? M_WAITOK : M_NOWAIT));
if (tmphhh == NULL)
return (ENOMEM);
tmphhh->hhh_type = hhook_type;
tmphhh->hhh_id = hhook_id;
tmphhh->hhh_nhooks = 0;
STAILQ_INIT(&tmphhh->hhh_hooks);
HHH_LOCK_INIT(tmphhh);
refcount_init(&tmphhh->hhh_refcount, 1);
HHHLIST_LOCK();
if (flags & HHOOK_HEADISINVNET) {
tmphhh->hhh_flags |= HHH_ISINVNET;
#ifdef VIMAGE
KASSERT(curvnet != NULL, ("curvnet is NULL"));
tmphhh->hhh_vid = (uintptr_t)curvnet;
LIST_INSERT_HEAD(&V_hhook_vhead_list, tmphhh, hhh_vnext);
#endif
}
LIST_INSERT_HEAD(&hhook_head_list, tmphhh, hhh_next);
n_hhookheads++;
HHHLIST_UNLOCK();
khelp_new_hhook_registered(tmphhh, flags);
if (hhh != NULL)
*hhh = tmphhh;
else
refcount_release(&tmphhh->hhh_refcount);
return (0);
}
static void
hhook_head_destroy(struct hhook_head *hhh)
{
struct hhook *tmp, *tmp2;
HHHLIST_LOCK_ASSERT();
KASSERT(n_hhookheads > 0, ("n_hhookheads should be > 0"));
LIST_REMOVE(hhh, hhh_next);
#ifdef VIMAGE
if (hhook_head_is_virtualised(hhh) == HHOOK_HEADISINVNET)
LIST_REMOVE(hhh, hhh_vnext);
#endif
HHH_WLOCK(hhh);
STAILQ_FOREACH_SAFE(tmp, &hhh->hhh_hooks, hhk_next, tmp2)
free(tmp, M_HHOOK);
HHH_WUNLOCK(hhh);
HHH_LOCK_DESTROY(hhh);
free(hhh, M_HHOOK);
n_hhookheads--;
}
int
hhook_head_deregister(struct hhook_head *hhh)
{
int error;
error = 0;
HHHLIST_LOCK();
if (hhh == NULL)
error = ENOENT;
else if (hhh->hhh_refcount > 1)
error = EBUSY;
else
hhook_head_destroy(hhh);
HHHLIST_UNLOCK();
return (error);
}
int
hhook_head_deregister_lookup(int32_t hhook_type, int32_t hhook_id)
{
struct hhook_head *hhh;
int error;
hhh = hhook_head_get(hhook_type, hhook_id);
error = hhook_head_deregister(hhh);
if (error == EBUSY)
hhook_head_release(hhh);
return (error);
}
struct hhook_head *
hhook_head_get(int32_t hhook_type, int32_t hhook_id)
{
struct hhook_head *hhh;
HHHLIST_LOCK();
LIST_FOREACH(hhh, &hhook_head_list, hhh_next) {
if (hhh->hhh_type == hhook_type && hhh->hhh_id == hhook_id) {
#ifdef VIMAGE
if (hhook_head_is_virtualised(hhh) ==
HHOOK_HEADISINVNET) {
KASSERT(curvnet != NULL, ("curvnet is NULL"));
if (hhh->hhh_vid != (uintptr_t)curvnet)
continue;
}
#endif
refcount_acquire(&hhh->hhh_refcount);
break;
}
}
HHHLIST_UNLOCK();
return (hhh);
}
void
hhook_head_release(struct hhook_head *hhh)
{
refcount_release(&hhh->hhh_refcount);
}
uint32_t
hhook_head_is_virtualised(struct hhook_head *hhh)
{
uint32_t ret;
ret = 0;
if (hhh != NULL) {
if (hhh->hhh_flags & HHH_ISINVNET)
ret = HHOOK_HEADISINVNET;
}
return (ret);
}
uint32_t
hhook_head_is_virtualised_lookup(int32_t hook_type, int32_t hook_id)
{
struct hhook_head *hhh;
uint32_t ret;
hhh = hhook_head_get(hook_type, hook_id);
if (hhh == NULL)
return (0);
ret = hhook_head_is_virtualised(hhh);
hhook_head_release(hhh);
return (ret);
}
static void
hhook_vnet_init(const void *unused __unused)
{
LIST_INIT(&V_hhook_vhead_list);
}
static void
hhook_vnet_uninit(const void *unused __unused)
{
struct hhook_head *hhh, *tmphhh;
HHHLIST_LOCK();
LIST_FOREACH_SAFE(hhh, &V_hhook_vhead_list, hhh_vnext, tmphhh) {
printf("%s: hhook_head type=%d, id=%d cleanup required\n",
__func__, hhh->hhh_type, hhh->hhh_id);
hhook_head_destroy(hhh);
}
HHHLIST_UNLOCK();
}
VNET_SYSINIT(hhook_vnet_init, SI_SUB_INIT_IF, SI_ORDER_FIRST,
hhook_vnet_init, NULL);
VNET_SYSUNINIT(hhook_vnet_uninit, SI_SUB_INIT_IF, SI_ORDER_FIRST,
hhook_vnet_uninit, NULL);