xref: /netbsd/sys/dev/dm/dm_target_delay.c (revision 096b14fe)
1 /*        $NetBSD: dm_target_delay.c,v 1.2 2020/01/21 16:27:53 tkusumi Exp $      */
2 
3 /*
4  * Copyright (c) 2020 The NetBSD Foundation, Inc.
5  * Copyright (c) 2015 The DragonFly Project.  All rights reserved.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Tomohiro Kusumi <tkusumi@netbsd.org>.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: dm_target_delay.c,v 1.2 2020/01/21 16:27:53 tkusumi Exp $");
34 
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/kernel.h>
38 #include <sys/buf.h>
39 #include <sys/kmem.h>
40 #include <sys/mutex.h>
41 #include <sys/queue.h>
42 #include <sys/callout.h>
43 #include <sys/pool.h>
44 #include <sys/kthread.h>
45 
46 #include "dm.h"
47 
48 struct dm_delay_buf {
49 	TAILQ_ENTRY(dm_delay_buf) entry;
50 	struct buf *bp;
51 	int expire;
52 };
53 TAILQ_HEAD(dm_delay_buf_list, dm_delay_buf);
54 
55 struct dm_delay_info {
56 	dm_pdev_t *pdev;
57 	uint64_t offset;
58 	int delay;
59 	int count;
60 	int enabled;
61 	struct dm_delay_buf_list buf_list;
62 	struct callout cal;
63 	kmutex_t buf_mtx;
64 	kmutex_t cal_mtx;
65 	kmutex_t token; /* lwkt_token in DragonFly */
66 };
67 
68 typedef struct target_delay_config {
69 	struct dm_delay_info read;
70 	struct dm_delay_info write;
71 	int argc; /* either 3 or 6 */
72 } dm_target_delay_config_t;
73 
74 static int _init(struct dm_delay_info*, char**);
75 static int _table(struct dm_delay_info*, char*);
76 static void _strategy(struct dm_delay_info*, struct buf*);
77 static __inline void _submit(struct dm_delay_info*, struct buf*);
78 static void _destroy(struct dm_delay_info*);
79 static void _timeout(void*);
80 static void _thread(void*);
81 static __inline void _debug(struct dm_delay_info*, const char*);
82 
83 static struct pool pool;
84 static bool pool_initialized = false;
85 
86 #ifdef DM_TARGET_MODULE
87 /*
88  * Every target can be compiled directly to dm driver or as a
89  * separate module this part of target is used for loading targets
90  * to dm driver.
91  * Target can be unloaded from kernel only if there are no users of
92  * it e.g. there are no devices which uses that target.
93  */
94 #include <sys/kernel.h>
95 #include <sys/module.h>
96 
97 MODULE(MODULE_CLASS_MISC, dm_target_delay, NULL);
98 
99 static int
dm_target_delay_modcmd(modcmd_t cmd,void * arg)100 dm_target_delay_modcmd(modcmd_t cmd, void *arg)
101 {
102 	dm_target_t *dmt;
103 	int r;
104 
105 	switch (cmd) {
106 	case MODULE_CMD_INIT:
107 		if ((dmt = dm_target_lookup("delay")) != NULL) {
108 			dm_target_unbusy(dmt);
109 			return EEXIST;
110 		}
111 		dmt = dm_target_alloc("delay");
112 
113 		dmt->version[0] = 1;
114 		dmt->version[1] = 0;
115 		dmt->version[2] = 0;
116 		dmt->init = &dm_target_delay_init;
117 		dmt->table = &dm_target_delay_table;
118 		dmt->strategy = &dm_target_delay_strategy;
119 		dmt->sync = &dm_target_delay_sync;
120 		dmt->destroy = &dm_target_delay_destroy;
121 		//dmt->upcall = &dm_target_delay_upcall;
122 		dmt->secsize = &dm_target_delay_secsize;
123 
124 		dm_target_delay_pool_create();
125 		r = dm_target_insert(dmt);
126 
127 		break;
128 
129 	case MODULE_CMD_FINI:
130 		r = dm_target_rem("delay");
131 		dm_target_delay_pool_destroy();
132 		break;
133 
134 	case MODULE_CMD_STAT:
135 		return ENOTTY;
136 
137 	default:
138 		return ENOTTY;
139 	}
140 
141 	return r;
142 }
143 #endif
144 
145 int
dm_target_delay_init(dm_table_entry_t * table_en,int argc,char ** argv)146 dm_target_delay_init(dm_table_entry_t *table_en, int argc, char **argv)
147 {
148 	dm_target_delay_config_t *tdc;
149 	int ret;
150 
151 	if (argc != 3 && argc != 6) {
152 		printf("Delay target takes 3 or 6 args, %d given\n", argc);
153 		return EINVAL;
154 	}
155 
156 	aprint_debug("Delay target init function called: argc=%d\n", argc);
157 
158 	tdc = kmem_alloc(sizeof(dm_target_delay_config_t), KM_SLEEP);
159 	tdc->argc = argc;
160 
161 	ret = _init(&tdc->read, argv);
162 	if (ret) {
163 		kmem_free(tdc, sizeof(*tdc));
164 		return ret;
165 	}
166 
167 	if (argc == 6)
168 		argv += 3;
169 
170 	ret = _init(&tdc->write, argv);
171 	if (ret) {
172 		dm_pdev_decr(tdc->read.pdev);
173 		kmem_free(tdc, sizeof(*tdc));
174 		return ret;
175 	}
176 
177 	dm_table_add_deps(table_en, tdc->read.pdev);
178 	dm_table_add_deps(table_en, tdc->write.pdev);
179 
180 	table_en->target_config = tdc;
181 
182 	return 0;
183 }
184 
185 static int
_init(struct dm_delay_info * di,char ** argv)186 _init(struct dm_delay_info *di, char **argv)
187 {
188 	dm_pdev_t *dmp;
189 	int tmp;
190 
191 	if (argv[0] == NULL)
192 		return EINVAL;
193 	if ((dmp = dm_pdev_insert(argv[0])) == NULL)
194 		return ENOENT;
195 
196 	di->pdev = dmp;
197 	di->offset = atoi64(argv[1]);
198 	tmp = atoi64(argv[2]);
199 	di->delay = tmp * hz / 1000;
200 	di->count = 0;
201 
202 	TAILQ_INIT(&di->buf_list);
203 	callout_init(&di->cal, 0);
204 	mutex_init(&di->buf_mtx, MUTEX_DEFAULT, IPL_NONE);
205 	mutex_init(&di->cal_mtx, MUTEX_DEFAULT, IPL_NONE);
206 	mutex_init(&di->token, MUTEX_DEFAULT, IPL_NONE);
207 
208 	di->enabled = 1;
209 	if (kthread_create(PRI_NONE, 0, NULL, _thread, di, NULL,
210 	    "dmdlthread")) {
211 		printf("Failed to create delay target's kthread\n");
212 		/* XXX cleanup ? */
213 		return EINVAL;
214 	}
215 
216 	_debug(di, "init");
217 	return 0;
218 }
219 
220 char *
dm_target_delay_info(void * target_config)221 dm_target_delay_info(void *target_config)
222 {
223 	dm_target_delay_config_t *tdc;
224 	char *params;
225 
226 	tdc = target_config;
227 	KASSERT(tdc != NULL);
228 
229 	aprint_debug("Delay target info function called\n");
230 
231 	params = kmem_alloc(DM_MAX_PARAMS_SIZE, KM_SLEEP);
232 	snprintf(params, DM_MAX_PARAMS_SIZE, "%d %d", tdc->read.count,
233 	    tdc->write.count);
234 
235 	return params;
236 }
237 
238 char *
dm_target_delay_table(void * target_config)239 dm_target_delay_table(void *target_config)
240 {
241 	dm_target_delay_config_t *tdc;
242 	char *params, *p;
243 
244 	tdc = target_config;
245 	KASSERT(tdc != NULL);
246 
247 	aprint_debug("Delay target table function called\n");
248 
249 	params = kmem_alloc(DM_MAX_PARAMS_SIZE, KM_SLEEP);
250 	p = params;
251 	p += _table(&tdc->read, p);
252 	if (tdc->argc == 6) {
253 		p += snprintf(p, DM_MAX_PARAMS_SIZE, " ");
254 		_table(&tdc->write, p);
255 	}
256 
257 	return params;
258 }
259 
260 static int
_table(struct dm_delay_info * di,char * p)261 _table(struct dm_delay_info *di, char *p)
262 {
263 
264 	return snprintf(p, DM_MAX_PARAMS_SIZE, "%s %" PRIu64 " %d",
265 	    di->pdev->udev_name, di->offset, di->delay);
266 }
267 
268 int
dm_target_delay_strategy(dm_table_entry_t * table_en,struct buf * bp)269 dm_target_delay_strategy(dm_table_entry_t *table_en, struct buf *bp)
270 {
271 	dm_target_delay_config_t *tdc;
272 	struct dm_delay_info *di;
273 
274 	tdc = table_en->target_config;
275 	KASSERT(tdc != NULL);
276 
277 	if (bp->b_flags & B_READ)
278 		di = &tdc->read;
279 	else
280 		di = &tdc->write;
281 
282 	if (di) {
283 		if (di->delay) {
284 			_strategy(di, bp);
285 		} else {
286 			_submit(di, bp);
287 		}
288 	} else {
289 		struct vnode *vnode = tdc->write.pdev->pdev_vnode;
290 		KASSERT(0);
291 		VOP_STRATEGY(vnode, bp);
292 	}
293 
294 	return 0;
295 }
296 
297 static void
_strategy(struct dm_delay_info * di,struct buf * bp)298 _strategy(struct dm_delay_info *di, struct buf *bp)
299 {
300 	struct dm_delay_buf *dp;
301 
302 	dp = pool_get(&pool, PR_WAITOK);
303 	dp->bp = bp;
304 	dp->expire = tick + di->delay;
305 
306 	mutex_enter(&di->buf_mtx);
307 	di->count++;
308 	TAILQ_INSERT_TAIL(&di->buf_list, dp, entry);
309 	mutex_exit(&di->buf_mtx);
310 
311 	mutex_enter(&di->cal_mtx);
312 	if (!callout_pending(&di->cal))
313 		callout_reset(&di->cal, di->delay, _timeout, di);
314 	mutex_exit(&di->cal_mtx);
315 }
316 
317 static __inline void
_submit(struct dm_delay_info * di,struct buf * bp)318 _submit(struct dm_delay_info *di, struct buf *bp)
319 {
320 
321 	_debug(di, "submit");
322 
323 	bp->b_blkno += di->offset;
324 	VOP_STRATEGY(di->pdev->pdev_vnode, bp);
325 }
326 
327 static void
_submit_queue(struct dm_delay_info * di,int submit_all)328 _submit_queue(struct dm_delay_info *di, int submit_all)
329 {
330 	struct dm_delay_buf *dp;
331 	struct dm_delay_buf_list tmp_list;
332 	int next = -1;
333 	int reset = 0;
334 
335 	_debug(di, "submitq");
336 	TAILQ_INIT(&tmp_list);
337 
338 	mutex_enter(&di->buf_mtx);
339 	while ((dp = TAILQ_FIRST(&di->buf_list)) != NULL) {
340 		if (submit_all || tick > dp->expire) {
341 			TAILQ_REMOVE(&di->buf_list, dp, entry);
342 			TAILQ_INSERT_TAIL(&tmp_list, dp, entry);
343 			di->count--;
344 			continue;
345 		}
346 		if (reset == 0) {
347 			reset = 1;
348 			next = dp->expire;
349 		} else {
350 			next = lmin(next, dp->expire);
351 		}
352 	}
353 	mutex_exit(&di->buf_mtx);
354 
355 	if (reset) {
356 		mutex_enter(&di->cal_mtx);
357 		callout_reset(&di->cal, next - tick, _timeout, di);
358 		mutex_exit(&di->cal_mtx);
359 	}
360 
361 	while ((dp = TAILQ_FIRST(&tmp_list)) != NULL) {
362 		TAILQ_REMOVE(&tmp_list, dp, entry);
363 		_submit(di, dp->bp);
364 		pool_put(&pool, dp);
365 	}
366 }
367 
368 int
dm_target_delay_sync(dm_table_entry_t * table_en)369 dm_target_delay_sync(dm_table_entry_t *table_en)
370 {
371 	dm_target_delay_config_t *tdc;
372 	int cmd;
373 
374 	tdc = table_en->target_config;
375 	cmd = 1;
376 
377 	return VOP_IOCTL(tdc->write.pdev->pdev_vnode, DIOCCACHESYNC, &cmd,
378 	    FREAD | FWRITE, kauth_cred_get());
379 }
380 
381 int
dm_target_delay_destroy(dm_table_entry_t * table_en)382 dm_target_delay_destroy(dm_table_entry_t *table_en)
383 {
384 	dm_target_delay_config_t *tdc;
385 
386 	tdc = table_en->target_config;
387 	if (tdc == NULL)
388 		goto out;
389 
390 	_destroy(&tdc->read);
391 	_destroy(&tdc->write);
392 
393 	kmem_free(tdc, sizeof(*tdc));
394 out:
395 	/* Unbusy target so we can unload it */
396 	dm_target_unbusy(table_en->target);
397 
398 	return 0;
399 }
400 
401 static void
_destroy(struct dm_delay_info * di)402 _destroy(struct dm_delay_info *di)
403 {
404 
405 	_debug(di, "destroy");
406 
407 	mutex_enter(&di->token);
408 	di->enabled = 0;
409 
410 	mutex_enter(&di->cal_mtx);
411 	if (callout_pending(&di->cal))
412 		callout_halt(&di->cal, NULL);
413 	mutex_exit(&di->cal_mtx);
414 
415 	_submit_queue(di, 1);
416 	mutex_exit(&di->token);
417 	wakeup(di);
418 	/* XXX race here (DragonFly uses lwkt_token instead of mutex) */
419 	tsleep(&di->enabled, 0, "dmdldestroy", 0);
420 
421 	mutex_destroy(&di->token);
422 	mutex_destroy(&di->cal_mtx);
423 	mutex_destroy(&di->buf_mtx);
424 
425 	dm_pdev_decr(di->pdev);
426 }
427 
428 #if 0
429 int
430 dm_target_delay_upcall(dm_table_entry_t *table_en, struct buf *bp)
431 {
432 
433 	return 0;
434 }
435 #endif
436 
437 int
dm_target_delay_secsize(dm_table_entry_t * table_en,unsigned int * secsizep)438 dm_target_delay_secsize(dm_table_entry_t *table_en, unsigned int *secsizep)
439 {
440 	dm_target_delay_config_t *tdc;
441 	unsigned int secsize;
442 
443 	secsize = 0;
444 
445 	tdc = table_en->target_config;
446 	if (tdc != NULL)
447 		secsize = tdc->write.pdev->pdev_secsize;
448 
449 	*secsizep = secsize;
450 
451 	return 0;
452 }
453 
454 static void
_timeout(void * arg)455 _timeout(void *arg)
456 {
457 	struct dm_delay_info *di = arg;
458 
459 	_debug(di, "timeout");
460 	wakeup(di);
461 }
462 
463 static void
_thread(void * arg)464 _thread(void *arg)
465 {
466 	struct dm_delay_info *di = arg;
467 
468 	_debug(di, "thread init");
469 	mutex_enter(&di->token);
470 
471 	while (di->enabled) {
472 		mutex_exit(&di->token);
473 		tsleep(di, 0, "dmdlthread", 0);
474 		_submit_queue(di, 0);
475 		mutex_enter(&di->token);
476 	}
477 
478 	mutex_exit(&di->token);
479 	wakeup(&di->enabled);
480 
481 	_debug(di, "thread exit");
482 	kthread_exit(0);
483 }
484 
485 static __inline void
_debug(struct dm_delay_info * di,const char * msg)486 _debug(struct dm_delay_info *di, const char *msg)
487 {
488 
489 	aprint_debug("%-8s: %d pdev=%s offset=%ju delay=%d count=%d\n",
490 	    msg, di->enabled, di->pdev->name, (uintmax_t)di->offset, di->delay,
491 	    di->count);
492 }
493 
494 void
dm_target_delay_pool_create(void)495 dm_target_delay_pool_create(void)
496 {
497 
498 	if (!pool_initialized) {
499 		pool_init(&pool, sizeof(struct dm_delay_buf), 0, 0, 0,
500 		    "dmdlobj", &pool_allocator_nointr, IPL_NONE);
501 		pool_initialized = true;
502 		aprint_debug("Delay target pool initialized\n");
503 	}
504 }
505 
506 void
dm_target_delay_pool_destroy(void)507 dm_target_delay_pool_destroy(void)
508 {
509 
510 	if (pool_initialized) {
511 		pool_destroy(&pool);
512 		pool_initialized = false;
513 		aprint_debug("Delay target pool destroyed\n");
514 	}
515 }
516