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