]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/cddl/contrib/opensolaris/uts/common/fs/zfs/trim_map.c
Upgrade to Unbound 1.5.1. Almost all our local changes to date have been
[FreeBSD/FreeBSD.git] / sys / cddl / contrib / opensolaris / uts / common / fs / zfs / trim_map.c
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
23  * All rights reserved.
24  */
25
26 #include <sys/zfs_context.h>
27 #include <sys/spa_impl.h>
28 #include <sys/vdev_impl.h>
29 #include <sys/trim_map.h>
30 #include <sys/time.h>
31
32 /*
33  * Calculate the zio end, upgrading based on ashift which would be
34  * done by zio_vdev_io_start.
35  *
36  * This makes free range consolidation much more effective
37  * than it would otherwise be as well as ensuring that entire
38  * blocks are invalidated by writes.
39  */
40 #define TRIM_ZIO_END(vd, offset, size)  (offset +               \
41         P2ROUNDUP(size, 1ULL << vd->vdev_top->vdev_ashift))
42
43 #define TRIM_MAP_SINC(tm, size)                                 \
44         atomic_add_64(&(tm)->tm_bytes, (size))
45
46 #define TRIM_MAP_SDEC(tm, size)                                 \
47         atomic_add_64(&(tm)->tm_bytes, -(size))
48
49 #define TRIM_MAP_QINC(tm)                                       \
50         atomic_inc_64(&(tm)->tm_pending);                       \
51
52 #define TRIM_MAP_QDEC(tm)                                       \
53         atomic_dec_64(&(tm)->tm_pending);
54
55 typedef struct trim_map {
56         list_t          tm_head;                /* List of segments sorted by txg. */
57         avl_tree_t      tm_queued_frees;        /* AVL tree of segments waiting for TRIM. */
58         avl_tree_t      tm_inflight_frees;      /* AVL tree of in-flight TRIMs. */
59         avl_tree_t      tm_inflight_writes;     /* AVL tree of in-flight writes. */
60         list_t          tm_pending_writes;      /* Writes blocked on in-flight frees. */
61         kmutex_t        tm_lock;
62         uint64_t        tm_pending;             /* Count of pending TRIMs. */
63         uint64_t        tm_bytes;               /* Total size in bytes of queued TRIMs. */
64 } trim_map_t;
65
66 typedef struct trim_seg {
67         avl_node_t      ts_node;        /* AVL node. */
68         list_node_t     ts_next;        /* List element. */
69         uint64_t        ts_start;       /* Starting offset of this segment. */
70         uint64_t        ts_end;         /* Ending offset (non-inclusive). */
71         uint64_t        ts_txg;         /* Segment creation txg. */
72         hrtime_t        ts_time;        /* Segment creation time. */
73 } trim_seg_t;
74
75 extern boolean_t zfs_trim_enabled;
76
77 static u_int trim_txg_delay = 32;
78 static u_int trim_timeout = 30;
79 static u_int trim_max_interval = 1;
80 /* Limit outstanding TRIMs to 2G (max size for a single TRIM request) */
81 static uint64_t trim_vdev_max_bytes = 2147483648;
82 /* Limit outstanding TRIMs to 64 (max ranges for a single TRIM request) */      
83 static u_int trim_vdev_max_pending = 64;
84
85 SYSCTL_DECL(_vfs_zfs);
86 SYSCTL_NODE(_vfs_zfs, OID_AUTO, trim, CTLFLAG_RD, 0, "ZFS TRIM");
87
88 SYSCTL_UINT(_vfs_zfs_trim, OID_AUTO, txg_delay, CTLFLAG_RWTUN, &trim_txg_delay,
89     0, "Delay TRIMs by up to this many TXGs");
90 SYSCTL_UINT(_vfs_zfs_trim, OID_AUTO, timeout, CTLFLAG_RWTUN, &trim_timeout, 0,
91     "Delay TRIMs by up to this many seconds");
92 SYSCTL_UINT(_vfs_zfs_trim, OID_AUTO, max_interval, CTLFLAG_RWTUN,
93     &trim_max_interval, 0,
94     "Maximum interval between TRIM queue processing (seconds)");
95
96 SYSCTL_DECL(_vfs_zfs_vdev);
97 SYSCTL_QUAD(_vfs_zfs_vdev, OID_AUTO, trim_max_bytes, CTLFLAG_RWTUN,
98     &trim_vdev_max_bytes, 0,
99     "Maximum pending TRIM bytes for a vdev");
100 SYSCTL_UINT(_vfs_zfs_vdev, OID_AUTO, trim_max_pending, CTLFLAG_RWTUN,
101     &trim_vdev_max_pending, 0,
102     "Maximum pending TRIM segments for a vdev");
103
104 static void trim_map_vdev_commit_done(spa_t *spa, vdev_t *vd);
105
106 static int
107 trim_map_seg_compare(const void *x1, const void *x2)
108 {
109         const trim_seg_t *s1 = x1;
110         const trim_seg_t *s2 = x2;
111
112         if (s1->ts_start < s2->ts_start) {
113                 if (s1->ts_end > s2->ts_start)
114                         return (0);
115                 return (-1);
116         }
117         if (s1->ts_start > s2->ts_start) {
118                 if (s1->ts_start < s2->ts_end)
119                         return (0);
120                 return (1);
121         }
122         return (0);
123 }
124
125 static int
126 trim_map_zio_compare(const void *x1, const void *x2)
127 {
128         const zio_t *z1 = x1;
129         const zio_t *z2 = x2;
130
131         if (z1->io_offset < z2->io_offset) {
132                 if (z1->io_offset + z1->io_size > z2->io_offset)
133                         return (0);
134                 return (-1);
135         }
136         if (z1->io_offset > z2->io_offset) {
137                 if (z1->io_offset < z2->io_offset + z2->io_size)
138                         return (0);
139                 return (1);
140         }
141         return (0);
142 }
143
144 void
145 trim_map_create(vdev_t *vd)
146 {
147         trim_map_t *tm;
148
149         ASSERT(zfs_trim_enabled && !vd->vdev_notrim &&
150                 vd->vdev_ops->vdev_op_leaf);
151
152         tm = kmem_zalloc(sizeof (*tm), KM_SLEEP);
153         mutex_init(&tm->tm_lock, NULL, MUTEX_DEFAULT, NULL);
154         list_create(&tm->tm_head, sizeof (trim_seg_t),
155             offsetof(trim_seg_t, ts_next));
156         list_create(&tm->tm_pending_writes, sizeof (zio_t),
157             offsetof(zio_t, io_trim_link));
158         avl_create(&tm->tm_queued_frees, trim_map_seg_compare,
159             sizeof (trim_seg_t), offsetof(trim_seg_t, ts_node));
160         avl_create(&tm->tm_inflight_frees, trim_map_seg_compare,
161             sizeof (trim_seg_t), offsetof(trim_seg_t, ts_node));
162         avl_create(&tm->tm_inflight_writes, trim_map_zio_compare,
163             sizeof (zio_t), offsetof(zio_t, io_trim_node));
164         vd->vdev_trimmap = tm;
165 }
166
167 void
168 trim_map_destroy(vdev_t *vd)
169 {
170         trim_map_t *tm;
171         trim_seg_t *ts;
172
173         ASSERT(vd->vdev_ops->vdev_op_leaf);
174
175         if (!zfs_trim_enabled)
176                 return;
177
178         tm = vd->vdev_trimmap;
179         if (tm == NULL)
180                 return;
181
182         /*
183          * We may have been called before trim_map_vdev_commit_done()
184          * had a chance to run, so do it now to prune the remaining
185          * inflight frees.
186          */
187         trim_map_vdev_commit_done(vd->vdev_spa, vd);
188
189         mutex_enter(&tm->tm_lock);
190         while ((ts = list_head(&tm->tm_head)) != NULL) {
191                 avl_remove(&tm->tm_queued_frees, ts);
192                 list_remove(&tm->tm_head, ts);
193                 kmem_free(ts, sizeof (*ts));
194                 TRIM_MAP_SDEC(tm, ts->ts_end - ts->ts_start);
195                 TRIM_MAP_QDEC(tm);
196         }
197         mutex_exit(&tm->tm_lock);
198
199         avl_destroy(&tm->tm_queued_frees);
200         avl_destroy(&tm->tm_inflight_frees);
201         avl_destroy(&tm->tm_inflight_writes);
202         list_destroy(&tm->tm_pending_writes);
203         list_destroy(&tm->tm_head);
204         mutex_destroy(&tm->tm_lock);
205         kmem_free(tm, sizeof (*tm));
206         vd->vdev_trimmap = NULL;
207 }
208
209 static void
210 trim_map_segment_add(trim_map_t *tm, uint64_t start, uint64_t end, uint64_t txg)
211 {
212         avl_index_t where;
213         trim_seg_t tsearch, *ts_before, *ts_after, *ts;
214         boolean_t merge_before, merge_after;
215         hrtime_t time;
216
217         ASSERT(MUTEX_HELD(&tm->tm_lock));
218         VERIFY(start < end);
219
220         time = gethrtime();
221         tsearch.ts_start = start;
222         tsearch.ts_end = end;
223
224         ts = avl_find(&tm->tm_queued_frees, &tsearch, &where);
225         if (ts != NULL) {
226                 if (start < ts->ts_start)
227                         trim_map_segment_add(tm, start, ts->ts_start, txg);
228                 if (end > ts->ts_end)
229                         trim_map_segment_add(tm, ts->ts_end, end, txg);
230                 return;
231         }
232
233         ts_before = avl_nearest(&tm->tm_queued_frees, where, AVL_BEFORE);
234         ts_after = avl_nearest(&tm->tm_queued_frees, where, AVL_AFTER);
235
236         merge_before = (ts_before != NULL && ts_before->ts_end == start);
237         merge_after = (ts_after != NULL && ts_after->ts_start == end);
238
239         if (merge_before && merge_after) {
240                 TRIM_MAP_SINC(tm, ts_after->ts_start - ts_before->ts_end);
241                 TRIM_MAP_QDEC(tm);
242                 avl_remove(&tm->tm_queued_frees, ts_before);
243                 list_remove(&tm->tm_head, ts_before);
244                 ts_after->ts_start = ts_before->ts_start;
245                 ts_after->ts_txg = txg;
246                 ts_after->ts_time = time;
247                 kmem_free(ts_before, sizeof (*ts_before));
248         } else if (merge_before) {
249                 TRIM_MAP_SINC(tm, end - ts_before->ts_end);
250                 ts_before->ts_end = end;
251                 ts_before->ts_txg = txg;
252                 ts_before->ts_time = time;
253         } else if (merge_after) {
254                 TRIM_MAP_SINC(tm, ts_after->ts_start - start);
255                 ts_after->ts_start = start;
256                 ts_after->ts_txg = txg;
257                 ts_after->ts_time = time;
258         } else {
259                 TRIM_MAP_SINC(tm, end - start);
260                 TRIM_MAP_QINC(tm);
261                 ts = kmem_alloc(sizeof (*ts), KM_SLEEP);
262                 ts->ts_start = start;
263                 ts->ts_end = end;
264                 ts->ts_txg = txg;
265                 ts->ts_time = time;
266                 avl_insert(&tm->tm_queued_frees, ts, where);
267                 list_insert_tail(&tm->tm_head, ts);
268         }
269 }
270
271 static void
272 trim_map_segment_remove(trim_map_t *tm, trim_seg_t *ts, uint64_t start,
273     uint64_t end)
274 {
275         trim_seg_t *nts;
276         boolean_t left_over, right_over;
277
278         ASSERT(MUTEX_HELD(&tm->tm_lock));
279
280         left_over = (ts->ts_start < start);
281         right_over = (ts->ts_end > end);
282
283         TRIM_MAP_SDEC(tm, end - start);
284         if (left_over && right_over) {
285                 nts = kmem_alloc(sizeof (*nts), KM_SLEEP);
286                 nts->ts_start = end;
287                 nts->ts_end = ts->ts_end;
288                 nts->ts_txg = ts->ts_txg;
289                 nts->ts_time = ts->ts_time;
290                 ts->ts_end = start;
291                 avl_insert_here(&tm->tm_queued_frees, nts, ts, AVL_AFTER);
292                 list_insert_after(&tm->tm_head, ts, nts);
293                 TRIM_MAP_QINC(tm);
294         } else if (left_over) {
295                 ts->ts_end = start;
296         } else if (right_over) {
297                 ts->ts_start = end;
298         } else {
299                 avl_remove(&tm->tm_queued_frees, ts);
300                 list_remove(&tm->tm_head, ts);
301                 TRIM_MAP_QDEC(tm);
302                 kmem_free(ts, sizeof (*ts));
303         }
304 }
305
306 static void
307 trim_map_free_locked(trim_map_t *tm, uint64_t start, uint64_t end, uint64_t txg)
308 {
309         zio_t zsearch, *zs;
310
311         ASSERT(MUTEX_HELD(&tm->tm_lock));
312
313         zsearch.io_offset = start;
314         zsearch.io_size = end - start;
315
316         zs = avl_find(&tm->tm_inflight_writes, &zsearch, NULL);
317         if (zs == NULL) {
318                 trim_map_segment_add(tm, start, end, txg);
319                 return;
320         }
321         if (start < zs->io_offset)
322                 trim_map_free_locked(tm, start, zs->io_offset, txg);
323         if (zs->io_offset + zs->io_size < end)
324                 trim_map_free_locked(tm, zs->io_offset + zs->io_size, end, txg);
325 }
326
327 void
328 trim_map_free(vdev_t *vd, uint64_t offset, uint64_t size, uint64_t txg)
329 {
330         trim_map_t *tm = vd->vdev_trimmap;
331
332         if (!zfs_trim_enabled || vd->vdev_notrim || tm == NULL)
333                 return;
334
335         mutex_enter(&tm->tm_lock);
336         trim_map_free_locked(tm, offset, TRIM_ZIO_END(vd, offset, size), txg);
337         mutex_exit(&tm->tm_lock);
338 }
339
340 boolean_t
341 trim_map_write_start(zio_t *zio)
342 {
343         vdev_t *vd = zio->io_vd;
344         trim_map_t *tm = vd->vdev_trimmap;
345         trim_seg_t tsearch, *ts;
346         boolean_t left_over, right_over;
347         uint64_t start, end;
348
349         if (!zfs_trim_enabled || vd->vdev_notrim || tm == NULL)
350                 return (B_TRUE);
351
352         start = zio->io_offset;
353         end = TRIM_ZIO_END(zio->io_vd, start, zio->io_size);
354         tsearch.ts_start = start;
355         tsearch.ts_end = end;
356
357         mutex_enter(&tm->tm_lock);
358
359         /*
360          * Checking for colliding in-flight frees.
361          */
362         ts = avl_find(&tm->tm_inflight_frees, &tsearch, NULL);
363         if (ts != NULL) {
364                 list_insert_tail(&tm->tm_pending_writes, zio);
365                 mutex_exit(&tm->tm_lock);
366                 return (B_FALSE);
367         }
368
369         ts = avl_find(&tm->tm_queued_frees, &tsearch, NULL);
370         if (ts != NULL) {
371                 /*
372                  * Loop until all overlapping segments are removed.
373                  */
374                 do {
375                         trim_map_segment_remove(tm, ts, start, end);
376                         ts = avl_find(&tm->tm_queued_frees, &tsearch, NULL);
377                 } while (ts != NULL);
378         }
379         avl_add(&tm->tm_inflight_writes, zio);
380
381         mutex_exit(&tm->tm_lock);
382
383         return (B_TRUE);
384 }
385
386 void
387 trim_map_write_done(zio_t *zio)
388 {
389         vdev_t *vd = zio->io_vd;
390         trim_map_t *tm = vd->vdev_trimmap;
391
392         /*
393          * Don't check for vdev_notrim, since the write could have
394          * started before vdev_notrim was set.
395          */
396         if (!zfs_trim_enabled || tm == NULL)
397                 return;
398
399         mutex_enter(&tm->tm_lock);
400         /*
401          * Don't fail if the write isn't in the tree, since the write
402          * could have started after vdev_notrim was set.
403          */
404         if (zio->io_trim_node.avl_child[0] ||
405             zio->io_trim_node.avl_child[1] ||
406             AVL_XPARENT(&zio->io_trim_node) ||
407             tm->tm_inflight_writes.avl_root == &zio->io_trim_node)
408                 avl_remove(&tm->tm_inflight_writes, zio);
409         mutex_exit(&tm->tm_lock);
410 }
411
412 /*
413  * Return the oldest segment (the one with the lowest txg / time) or NULL if:
414  * 1. The list is empty
415  * 2. The first element's txg is greater than txgsafe
416  * 3. The first element's txg is not greater than the txg argument and the
417  *    the first element's time is not greater than time argument
418  */
419 static trim_seg_t *
420 trim_map_first(trim_map_t *tm, uint64_t txg, uint64_t txgsafe, hrtime_t time)
421 {
422         trim_seg_t *ts;
423
424         ASSERT(MUTEX_HELD(&tm->tm_lock));
425         VERIFY(txgsafe >= txg);
426
427         ts = list_head(&tm->tm_head);
428         if (ts != NULL && ts->ts_txg <= txgsafe &&
429             (ts->ts_txg <= txg || ts->ts_time <= time ||
430             tm->tm_bytes > trim_vdev_max_bytes ||
431             tm->tm_pending > trim_vdev_max_pending))
432                 return (ts);
433         return (NULL);
434 }
435
436 static void
437 trim_map_vdev_commit(spa_t *spa, zio_t *zio, vdev_t *vd)
438 {
439         trim_map_t *tm = vd->vdev_trimmap;
440         trim_seg_t *ts;
441         uint64_t size, offset, txgtarget, txgsafe;
442         hrtime_t timelimit;
443
444         ASSERT(vd->vdev_ops->vdev_op_leaf);
445
446         if (tm == NULL)
447                 return;
448
449         timelimit = gethrtime() - trim_timeout * NANOSEC;
450         if (vd->vdev_isl2cache) {
451                 txgsafe = UINT64_MAX;
452                 txgtarget = UINT64_MAX;
453         } else {
454                 txgsafe = MIN(spa_last_synced_txg(spa), spa_freeze_txg(spa));
455                 if (txgsafe > trim_txg_delay)
456                         txgtarget = txgsafe - trim_txg_delay;
457                 else
458                         txgtarget = 0;
459         }
460
461         mutex_enter(&tm->tm_lock);
462         /* Loop until we have sent all outstanding free's */
463         while ((ts = trim_map_first(tm, txgtarget, txgsafe, timelimit))
464             != NULL) {
465                 list_remove(&tm->tm_head, ts);
466                 avl_remove(&tm->tm_queued_frees, ts);
467                 avl_add(&tm->tm_inflight_frees, ts);
468                 size = ts->ts_end - ts->ts_start;
469                 offset = ts->ts_start;
470                 TRIM_MAP_SDEC(tm, size);
471                 TRIM_MAP_QDEC(tm);
472                 /*
473                  * We drop the lock while we call zio_nowait as the IO
474                  * scheduler can result in a different IO being run e.g.
475                  * a write which would result in a recursive lock.
476                  */
477                 mutex_exit(&tm->tm_lock);
478
479                 zio_nowait(zio_trim(zio, spa, vd, offset, size));
480
481                 mutex_enter(&tm->tm_lock);
482         }
483         mutex_exit(&tm->tm_lock);
484 }
485
486 static void
487 trim_map_vdev_commit_done(spa_t *spa, vdev_t *vd)
488 {
489         trim_map_t *tm = vd->vdev_trimmap;
490         trim_seg_t *ts;
491         list_t pending_writes;
492         zio_t *zio;
493         uint64_t start, size;
494         void *cookie;
495
496         ASSERT(vd->vdev_ops->vdev_op_leaf);
497
498         if (tm == NULL)
499                 return;
500
501         mutex_enter(&tm->tm_lock);
502         if (!avl_is_empty(&tm->tm_inflight_frees)) {
503                 cookie = NULL;
504                 while ((ts = avl_destroy_nodes(&tm->tm_inflight_frees,
505                     &cookie)) != NULL) {
506                         kmem_free(ts, sizeof (*ts));
507                 }
508         }
509         list_create(&pending_writes, sizeof (zio_t), offsetof(zio_t,
510             io_trim_link));
511         list_move_tail(&pending_writes, &tm->tm_pending_writes);
512         mutex_exit(&tm->tm_lock);
513
514         while ((zio = list_remove_head(&pending_writes)) != NULL) {
515                 zio_vdev_io_reissue(zio);
516                 zio_execute(zio);
517         }
518         list_destroy(&pending_writes);
519 }
520
521 static void
522 trim_map_commit(spa_t *spa, zio_t *zio, vdev_t *vd)
523 {
524         int c;
525
526         if (vd == NULL)
527                 return;
528
529         if (vd->vdev_ops->vdev_op_leaf) {
530                 trim_map_vdev_commit(spa, zio, vd);
531         } else {
532                 for (c = 0; c < vd->vdev_children; c++)
533                         trim_map_commit(spa, zio, vd->vdev_child[c]);
534         }
535 }
536
537 static void
538 trim_map_commit_done(spa_t *spa, vdev_t *vd)
539 {
540         int c;
541
542         if (vd == NULL)
543                 return;
544
545         if (vd->vdev_ops->vdev_op_leaf) {
546                 trim_map_vdev_commit_done(spa, vd);
547         } else {
548                 for (c = 0; c < vd->vdev_children; c++)
549                         trim_map_commit_done(spa, vd->vdev_child[c]);
550         }
551 }
552
553 static void
554 trim_thread(void *arg)
555 {
556         spa_t *spa = arg;
557         zio_t *zio;
558
559 #ifdef _KERNEL
560         (void) snprintf(curthread->td_name, sizeof(curthread->td_name),
561             "trim %s", spa_name(spa));
562 #endif
563
564         for (;;) {
565                 mutex_enter(&spa->spa_trim_lock);
566                 if (spa->spa_trim_thread == NULL) {
567                         spa->spa_trim_thread = curthread;
568                         cv_signal(&spa->spa_trim_cv);
569                         mutex_exit(&spa->spa_trim_lock);
570                         thread_exit();
571                 }
572
573                 (void) cv_timedwait(&spa->spa_trim_cv, &spa->spa_trim_lock,
574                     hz * trim_max_interval);
575                 mutex_exit(&spa->spa_trim_lock);
576
577                 zio = zio_root(spa, NULL, NULL, ZIO_FLAG_CANFAIL);
578
579                 spa_config_enter(spa, SCL_STATE, FTAG, RW_READER);
580                 trim_map_commit(spa, zio, spa->spa_root_vdev);
581                 (void) zio_wait(zio);
582                 trim_map_commit_done(spa, spa->spa_root_vdev);
583                 spa_config_exit(spa, SCL_STATE, FTAG);
584         }
585 }
586
587 void
588 trim_thread_create(spa_t *spa)
589 {
590
591         if (!zfs_trim_enabled)
592                 return;
593
594         mutex_init(&spa->spa_trim_lock, NULL, MUTEX_DEFAULT, NULL);
595         cv_init(&spa->spa_trim_cv, NULL, CV_DEFAULT, NULL);
596         mutex_enter(&spa->spa_trim_lock);
597         spa->spa_trim_thread = thread_create(NULL, 0, trim_thread, spa, 0, &p0,
598             TS_RUN, minclsyspri);
599         mutex_exit(&spa->spa_trim_lock);
600 }
601
602 void
603 trim_thread_destroy(spa_t *spa)
604 {
605
606         if (!zfs_trim_enabled)
607                 return;
608         if (spa->spa_trim_thread == NULL)
609                 return;
610
611         mutex_enter(&spa->spa_trim_lock);
612         /* Setting spa_trim_thread to NULL tells the thread to stop. */
613         spa->spa_trim_thread = NULL;
614         cv_signal(&spa->spa_trim_cv);
615         /* The thread will set it back to != NULL on exit. */
616         while (spa->spa_trim_thread == NULL)
617                 cv_wait(&spa->spa_trim_cv, &spa->spa_trim_lock);
618         spa->spa_trim_thread = NULL;
619         mutex_exit(&spa->spa_trim_lock);
620
621         cv_destroy(&spa->spa_trim_cv);
622         mutex_destroy(&spa->spa_trim_lock);
623 }
624
625 void
626 trim_thread_wakeup(spa_t *spa)
627 {
628
629         if (!zfs_trim_enabled)
630                 return;
631         if (spa->spa_trim_thread == NULL)
632                 return;
633
634         mutex_enter(&spa->spa_trim_lock);
635         cv_signal(&spa->spa_trim_cv);
636         mutex_exit(&spa->spa_trim_lock);
637 }