1216615Slstewart/*-
2251770Slstewart * Copyright (c) 2010,2013 Lawrence Stewart <lstewart@freebsd.org>
3216615Slstewart * Copyright (c) 2010 The FreeBSD Foundation
4216615Slstewart * All rights reserved.
5216615Slstewart *
6216615Slstewart * This software was developed by Lawrence Stewart while studying at the Centre
7220560Slstewart * for Advanced Internet Architectures, Swinburne University of Technology,
8220560Slstewart * made possible in part by grants from the FreeBSD Foundation and Cisco
9220560Slstewart * University Research Program Fund at Community Foundation Silicon Valley.
10216615Slstewart *
11216615Slstewart * Portions of this software were developed at the Centre for Advanced
12216615Slstewart * Internet Architectures, Swinburne University of Technology, Melbourne,
13216615Slstewart * Australia by Lawrence Stewart under sponsorship from the FreeBSD Foundation.
14216615Slstewart *
15216615Slstewart * Redistribution and use in source and binary forms, with or without
16216615Slstewart * modification, are permitted provided that the following conditions
17216615Slstewart * are met:
18216615Slstewart * 1. Redistributions of source code must retain the above copyright
19216615Slstewart *    notice, this list of conditions and the following disclaimer.
20216615Slstewart * 2. Redistributions in binary form must reproduce the above copyright
21216615Slstewart *    notice, this list of conditions and the following disclaimer in the
22216615Slstewart *    documentation and/or other materials provided with the distribution.
23216615Slstewart *
24216615Slstewart * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25216615Slstewart * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26216615Slstewart * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27216615Slstewart * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28216615Slstewart * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29216615Slstewart * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30216615Slstewart * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31216615Slstewart * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32216615Slstewart * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33216615Slstewart * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34216615Slstewart * SUCH DAMAGE.
35216615Slstewart */
36216615Slstewart
37216615Slstewart#include <sys/cdefs.h>
38216615Slstewart__FBSDID("$FreeBSD$");
39216615Slstewart
40216615Slstewart#include <sys/param.h>
41216615Slstewart#include <sys/kernel.h>
42216615Slstewart#include <sys/hhook.h>
43216615Slstewart#include <sys/khelp.h>
44216615Slstewart#include <sys/lock.h>
45216615Slstewart#include <sys/malloc.h>
46216615Slstewart#include <sys/module.h>
47216615Slstewart#include <sys/module_khelp.h>
48216615Slstewart#include <sys/osd.h>
49216615Slstewart#include <sys/queue.h>
50216615Slstewart#include <sys/refcount.h>
51216615Slstewart#include <sys/rwlock.h>
52216615Slstewart#include <sys/systm.h>
53216615Slstewart
54216615Slstewartstatic struct rwlock khelp_list_lock;
55216615SlstewartRW_SYSINIT(khelplistlock, &khelp_list_lock, "helper list lock");
56216615Slstewart
57216615Slstewartstatic TAILQ_HEAD(helper_head, helper) helpers = TAILQ_HEAD_INITIALIZER(helpers);
58216615Slstewart
59216615Slstewart/* Private function prototypes. */
60216615Slstewartstatic inline void khelp_remove_osd(struct helper *h, struct osd *hosd);
61251774Slstewartvoid khelp_new_hhook_registered(struct hhook_head *hhh, uint32_t flags);
62216615Slstewart
63216615Slstewart#define	KHELP_LIST_WLOCK() rw_wlock(&khelp_list_lock)
64216615Slstewart#define	KHELP_LIST_WUNLOCK() rw_wunlock(&khelp_list_lock)
65216615Slstewart#define	KHELP_LIST_RLOCK() rw_rlock(&khelp_list_lock)
66216615Slstewart#define	KHELP_LIST_RUNLOCK() rw_runlock(&khelp_list_lock)
67216615Slstewart#define	KHELP_LIST_LOCK_ASSERT() rw_assert(&khelp_list_lock, RA_LOCKED)
68216615Slstewart
69216615Slstewartint
70216615Slstewartkhelp_register_helper(struct helper *h)
71216615Slstewart{
72216615Slstewart	struct helper *tmph;
73216615Slstewart	int error, i, inserted;
74216615Slstewart
75251778Slstewart	error = inserted = 0;
76216615Slstewart	refcount_init(&h->h_refcount, 0);
77216615Slstewart	h->h_id = osd_register(OSD_KHELP, NULL, NULL);
78216615Slstewart
79216615Slstewart	/* It's only safe to add the hooks after osd_register(). */
80251778Slstewart	for (i = 0; i < h->h_nhooks && !error; i++) {
81251778Slstewart		/* We don't require the module to assign hook_helper. */
82251778Slstewart		h->h_hooks[i].hook_helper = h;
83251778Slstewart		error = hhook_add_hook_lookup(&h->h_hooks[i], HHOOK_WAITOK);
84251778Slstewart		if (error)
85251778Slstewart			printf("%s: \"%s\" khelp module unable to "
86251778Slstewart			    "hook type %d id %d due to error %d\n", __func__,
87251778Slstewart			    h->h_name, h->h_hooks[i].hook_type,
88251778Slstewart			    h->h_hooks[i].hook_id, error);
89216615Slstewart	}
90216615Slstewart
91251778Slstewart	if (error) {
92251778Slstewart		for (i--; i >= 0; i--)
93251778Slstewart			hhook_remove_hook_lookup(&h->h_hooks[i]);
94251778Slstewart		osd_deregister(OSD_KHELP, h->h_id);
95251778Slstewart	} else {
96216615Slstewart		KHELP_LIST_WLOCK();
97216615Slstewart		/*
98216615Slstewart		 * Keep list of helpers sorted in descending h_id order. Due to
99216615Slstewart		 * the way osd_set() works, a sorted list ensures
100251778Slstewart		 * khelp_init_osd() will operate with improved efficiency.
101216615Slstewart		 */
102216615Slstewart		TAILQ_FOREACH(tmph, &helpers, h_next) {
103216615Slstewart			if (tmph->h_id < h->h_id) {
104216615Slstewart				TAILQ_INSERT_BEFORE(tmph, h, h_next);
105216615Slstewart				inserted = 1;
106216615Slstewart				break;
107216615Slstewart			}
108216615Slstewart		}
109216615Slstewart
110216615Slstewart		if (!inserted)
111216615Slstewart			TAILQ_INSERT_TAIL(&helpers, h, h_next);
112216615Slstewart		KHELP_LIST_WUNLOCK();
113216615Slstewart	}
114216615Slstewart
115216615Slstewart	return (error);
116216615Slstewart}
117216615Slstewart
118216615Slstewartint
119216615Slstewartkhelp_deregister_helper(struct helper *h)
120216615Slstewart{
121216615Slstewart	struct helper *tmph;
122216615Slstewart	int error, i;
123216615Slstewart
124216615Slstewart	KHELP_LIST_WLOCK();
125216615Slstewart	if (h->h_refcount > 0)
126216615Slstewart		error = EBUSY;
127216615Slstewart	else {
128216615Slstewart		error = ENOENT;
129216615Slstewart		TAILQ_FOREACH(tmph, &helpers, h_next) {
130216615Slstewart			if (tmph == h) {
131216615Slstewart				TAILQ_REMOVE(&helpers, h, h_next);
132216615Slstewart				error = 0;
133216615Slstewart				break;
134216615Slstewart			}
135216615Slstewart		}
136216615Slstewart	}
137216615Slstewart	KHELP_LIST_WUNLOCK();
138216615Slstewart
139216615Slstewart	if (!error) {
140251778Slstewart		for (i = 0; i < h->h_nhooks; i++)
141251778Slstewart			hhook_remove_hook_lookup(&h->h_hooks[i]);
142216615Slstewart		osd_deregister(OSD_KHELP, h->h_id);
143216615Slstewart	}
144216615Slstewart
145216615Slstewart	return (error);
146216615Slstewart}
147216615Slstewart
148216615Slstewartint
149216615Slstewartkhelp_init_osd(uint32_t classes, struct osd *hosd)
150216615Slstewart{
151216615Slstewart	struct helper *h;
152216615Slstewart	void *hdata;
153216615Slstewart	int error;
154216615Slstewart
155216615Slstewart	KASSERT(hosd != NULL, ("struct osd not initialised!"));
156216615Slstewart
157216615Slstewart	error = 0;
158216615Slstewart
159216615Slstewart	KHELP_LIST_RLOCK();
160216615Slstewart	TAILQ_FOREACH(h, &helpers, h_next) {
161216615Slstewart		/* If helper is correct class and needs to store OSD... */
162216615Slstewart		if (h->h_classes & classes && h->h_flags & HELPER_NEEDS_OSD) {
163216615Slstewart			hdata = uma_zalloc(h->h_zone, M_NOWAIT);
164216615Slstewart			if (hdata == NULL) {
165216615Slstewart				error = ENOMEM;
166216615Slstewart				break;
167216615Slstewart			}
168216615Slstewart			osd_set(OSD_KHELP, hosd, h->h_id, hdata);
169216615Slstewart			refcount_acquire(&h->h_refcount);
170216615Slstewart		}
171216615Slstewart	}
172216615Slstewart
173216615Slstewart	if (error) {
174216615Slstewart		/* Delete OSD that was assigned prior to the error. */
175216615Slstewart		TAILQ_FOREACH(h, &helpers, h_next) {
176216615Slstewart			if (h->h_classes & classes)
177216615Slstewart				khelp_remove_osd(h, hosd);
178216615Slstewart		}
179216615Slstewart	}
180216615Slstewart	KHELP_LIST_RUNLOCK();
181216615Slstewart
182216615Slstewart	return (error);
183216615Slstewart}
184216615Slstewart
185216615Slstewartint
186216615Slstewartkhelp_destroy_osd(struct osd *hosd)
187216615Slstewart{
188216615Slstewart	struct helper *h;
189216615Slstewart	int error;
190216615Slstewart
191216615Slstewart	KASSERT(hosd != NULL, ("struct osd not initialised!"));
192216615Slstewart
193216615Slstewart	error = 0;
194216615Slstewart
195216615Slstewart	KHELP_LIST_RLOCK();
196216615Slstewart	/*
197216615Slstewart	 * Clean up all khelp related OSD.
198216615Slstewart	 *
199216615Slstewart	 * XXXLAS: Would be nice to use something like osd_exit() here but it
200216615Slstewart	 * doesn't have the right semantics for this purpose.
201216615Slstewart	 */
202216615Slstewart	TAILQ_FOREACH(h, &helpers, h_next)
203216615Slstewart		khelp_remove_osd(h, hosd);
204216615Slstewart	KHELP_LIST_RUNLOCK();
205216615Slstewart
206216615Slstewart	return (error);
207216615Slstewart}
208216615Slstewart
209216615Slstewartstatic inline void
210216615Slstewartkhelp_remove_osd(struct helper *h, struct osd *hosd)
211216615Slstewart{
212216615Slstewart	void *hdata;
213216615Slstewart
214216615Slstewart	if (h->h_flags & HELPER_NEEDS_OSD) {
215216615Slstewart		/*
216216615Slstewart		 * If the current helper uses OSD and calling osd_get()
217216615Slstewart		 * on the helper's h_id returns non-NULL, the helper has
218216615Slstewart		 * OSD attached to 'hosd' which needs to be cleaned up.
219216615Slstewart		 */
220216615Slstewart		hdata = osd_get(OSD_KHELP, hosd, h->h_id);
221216615Slstewart		if (hdata != NULL) {
222216615Slstewart			uma_zfree(h->h_zone, hdata);
223216615Slstewart			osd_del(OSD_KHELP, hosd, h->h_id);
224216615Slstewart			refcount_release(&h->h_refcount);
225216615Slstewart		}
226216615Slstewart	}
227216615Slstewart}
228216615Slstewart
229216615Slstewartvoid *
230216615Slstewartkhelp_get_osd(struct osd *hosd, int32_t id)
231216615Slstewart{
232216615Slstewart
233216615Slstewart	return (osd_get(OSD_KHELP, hosd, id));
234216615Slstewart}
235216615Slstewart
236216615Slstewartint32_t
237216615Slstewartkhelp_get_id(char *hname)
238216615Slstewart{
239216615Slstewart	struct helper *h;
240216615Slstewart	int32_t id;
241216615Slstewart
242216615Slstewart	id = -1;
243216615Slstewart
244216615Slstewart	KHELP_LIST_RLOCK();
245216615Slstewart	TAILQ_FOREACH(h, &helpers, h_next) {
246216615Slstewart		if (strncmp(h->h_name, hname, HELPER_NAME_MAXLEN) == 0) {
247216615Slstewart			id = h->h_id;
248216615Slstewart			break;
249216615Slstewart		}
250216615Slstewart	}
251216615Slstewart	KHELP_LIST_RUNLOCK();
252216615Slstewart
253216615Slstewart	return (id);
254216615Slstewart}
255216615Slstewart
256216615Slstewartint
257216615Slstewartkhelp_add_hhook(struct hookinfo *hki, uint32_t flags)
258216615Slstewart{
259216615Slstewart	int error;
260216615Slstewart
261216615Slstewart	/*
262251770Slstewart	 * XXXLAS: Should probably include the functionality to update the
263251770Slstewart	 * helper's h_hooks struct member.
264216615Slstewart	 */
265251770Slstewart	error = hhook_add_hook_lookup(hki, flags);
266216615Slstewart
267216615Slstewart	return (error);
268216615Slstewart}
269216615Slstewart
270216615Slstewartint
271216615Slstewartkhelp_remove_hhook(struct hookinfo *hki)
272216615Slstewart{
273216615Slstewart	int error;
274216615Slstewart
275216615Slstewart	/*
276251770Slstewart	 * XXXLAS: Should probably include the functionality to update the
277251770Slstewart	 * helper's h_hooks struct member.
278216615Slstewart	 */
279251770Slstewart	error = hhook_remove_hook_lookup(hki);
280216615Slstewart
281216615Slstewart	return (error);
282216615Slstewart}
283216615Slstewart
284251774Slstewart/*
285251774Slstewart * Private KPI between hhook and khelp that allows khelp modules to insert hook
286251774Slstewart * functions into hhook points which register after the modules were loaded.
287251774Slstewart */
288251774Slstewartvoid
289251774Slstewartkhelp_new_hhook_registered(struct hhook_head *hhh, uint32_t flags)
290251774Slstewart{
291251774Slstewart	struct helper *h;
292251774Slstewart	int error, i;
293251774Slstewart
294251774Slstewart	KHELP_LIST_RLOCK();
295251774Slstewart	TAILQ_FOREACH(h, &helpers, h_next) {
296251774Slstewart		for (i = 0; i < h->h_nhooks; i++) {
297251774Slstewart			if (hhh->hhh_type != h->h_hooks[i].hook_type ||
298251774Slstewart			    hhh->hhh_id != h->h_hooks[i].hook_id)
299251774Slstewart				continue;
300251774Slstewart			error = hhook_add_hook(hhh, &h->h_hooks[i], flags);
301251774Slstewart			if (error) {
302251774Slstewart				printf("%s: \"%s\" khelp module unable to "
303251774Slstewart				    "hook type %d id %d due to error %d\n",
304251774Slstewart				    __func__, h->h_name,
305251774Slstewart				    h->h_hooks[i].hook_type,
306251774Slstewart				    h->h_hooks[i].hook_id, error);
307251774Slstewart				error = 0;
308251774Slstewart			}
309251774Slstewart		}
310251774Slstewart	}
311251774Slstewart	KHELP_LIST_RUNLOCK();
312251774Slstewart}
313251774Slstewart
314216615Slstewartint
315216615Slstewartkhelp_modevent(module_t mod, int event_type, void *data)
316216615Slstewart{
317216615Slstewart	struct khelp_modevent_data *kmd;
318216615Slstewart	int error;
319216615Slstewart
320216615Slstewart	kmd = (struct khelp_modevent_data *)data;
321216615Slstewart	error = 0;
322216615Slstewart
323216615Slstewart	switch(event_type) {
324216615Slstewart	case MOD_LOAD:
325216615Slstewart		if (kmd->helper->h_flags & HELPER_NEEDS_OSD) {
326216615Slstewart			if (kmd->uma_zsize <= 0) {
327216615Slstewart				printf("Use KHELP_DECLARE_MOD_UMA() instead!\n");
328216615Slstewart				error = EDOOFUS;
329216615Slstewart				break;
330216615Slstewart			}
331216615Slstewart			kmd->helper->h_zone = uma_zcreate(kmd->name,
332216615Slstewart			    kmd->uma_zsize, kmd->umactor, kmd->umadtor, NULL,
333216615Slstewart			    NULL, 0, 0);
334216615Slstewart			if (kmd->helper->h_zone == NULL) {
335216615Slstewart				error = ENOMEM;
336216615Slstewart				break;
337216615Slstewart			}
338216615Slstewart		}
339216615Slstewart		strlcpy(kmd->helper->h_name, kmd->name, HELPER_NAME_MAXLEN);
340216615Slstewart		kmd->helper->h_hooks = kmd->hooks;
341216615Slstewart		kmd->helper->h_nhooks = kmd->nhooks;
342216615Slstewart		if (kmd->helper->mod_init != NULL)
343216615Slstewart			error = kmd->helper->mod_init();
344216615Slstewart		if (!error)
345216615Slstewart			error = khelp_register_helper(kmd->helper);
346216615Slstewart		break;
347216615Slstewart
348216615Slstewart	case MOD_QUIESCE:
349216615Slstewart	case MOD_SHUTDOWN:
350216615Slstewart	case MOD_UNLOAD:
351216615Slstewart		error = khelp_deregister_helper(kmd->helper);
352216615Slstewart		if (!error) {
353216615Slstewart			if (kmd->helper->h_flags & HELPER_NEEDS_OSD)
354216615Slstewart				uma_zdestroy(kmd->helper->h_zone);
355216615Slstewart			if (kmd->helper->mod_destroy != NULL)
356216615Slstewart				kmd->helper->mod_destroy();
357216615Slstewart		} else if (error == ENOENT)
358216615Slstewart			/* Do nothing and allow unload if helper not in list. */
359216615Slstewart			error = 0;
360216615Slstewart		else if (error == EBUSY)
361216615Slstewart			printf("Khelp module \"%s\" can't unload until its "
362216615Slstewart			    "refcount drops from %d to 0.\n", kmd->name,
363216615Slstewart			    kmd->helper->h_refcount);
364216615Slstewart		break;
365216615Slstewart
366216615Slstewart	default:
367216615Slstewart		error = EINVAL;
368216615Slstewart		break;
369216615Slstewart	}
370216615Slstewart
371216615Slstewart	return (error);
372216615Slstewart}
373