913 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (C) 2003 Sistina Software4 * Copyright (C) 2004-2008 Red Hat, Inc. All rights reserved.5 *6 * This file is released under the LGPL.7 */8 9#include <linux/init.h>10#include <linux/slab.h>11#include <linux/module.h>12#include <linux/vmalloc.h>13#include <linux/dm-io.h>14#include <linux/dm-dirty-log.h>15 16#include <linux/device-mapper.h>17 18#define DM_MSG_PREFIX "dirty region log"19 20static LIST_HEAD(_log_types);21static DEFINE_SPINLOCK(_lock);22 23static struct dm_dirty_log_type *__find_dirty_log_type(const char *name)24{25 struct dm_dirty_log_type *log_type;26 27 list_for_each_entry(log_type, &_log_types, list)28 if (!strcmp(name, log_type->name))29 return log_type;30 31 return NULL;32}33 34static struct dm_dirty_log_type *_get_dirty_log_type(const char *name)35{36 struct dm_dirty_log_type *log_type;37 38 spin_lock(&_lock);39 40 log_type = __find_dirty_log_type(name);41 if (log_type && !try_module_get(log_type->module))42 log_type = NULL;43 44 spin_unlock(&_lock);45 46 return log_type;47}48 49/*50 * get_type51 * @type_name52 *53 * Attempt to retrieve the dm_dirty_log_type by name. If not already54 * available, attempt to load the appropriate module.55 *56 * Log modules are named "dm-log-" followed by the 'type_name'.57 * Modules may contain multiple types.58 * This function will first try the module "dm-log-<type_name>",59 * then truncate 'type_name' on the last '-' and try again.60 *61 * For example, if type_name was "clustered-disk", it would search62 * 'dm-log-clustered-disk' then 'dm-log-clustered'.63 *64 * Returns: dirty_log_type* on success, NULL on failure65 */66static struct dm_dirty_log_type *get_type(const char *type_name)67{68 char *p, *type_name_dup;69 struct dm_dirty_log_type *log_type;70 71 if (!type_name)72 return NULL;73 74 log_type = _get_dirty_log_type(type_name);75 if (log_type)76 return log_type;77 78 type_name_dup = kstrdup(type_name, GFP_KERNEL);79 if (!type_name_dup) {80 DMWARN("No memory left to attempt log module load for \"%s\"",81 type_name);82 return NULL;83 }84 85 while (request_module("dm-log-%s", type_name_dup) ||86 !(log_type = _get_dirty_log_type(type_name))) {87 p = strrchr(type_name_dup, '-');88 if (!p)89 break;90 p[0] = '\0';91 }92 93 if (!log_type)94 DMWARN("Module for logging type \"%s\" not found.", type_name);95 96 kfree(type_name_dup);97 98 return log_type;99}100 101static void put_type(struct dm_dirty_log_type *type)102{103 if (!type)104 return;105 106 spin_lock(&_lock);107 if (!__find_dirty_log_type(type->name))108 goto out;109 110 module_put(type->module);111 112out:113 spin_unlock(&_lock);114}115 116int dm_dirty_log_type_register(struct dm_dirty_log_type *type)117{118 int r = 0;119 120 spin_lock(&_lock);121 if (!__find_dirty_log_type(type->name))122 list_add(&type->list, &_log_types);123 else124 r = -EEXIST;125 spin_unlock(&_lock);126 127 return r;128}129EXPORT_SYMBOL(dm_dirty_log_type_register);130 131int dm_dirty_log_type_unregister(struct dm_dirty_log_type *type)132{133 spin_lock(&_lock);134 135 if (!__find_dirty_log_type(type->name)) {136 spin_unlock(&_lock);137 return -EINVAL;138 }139 140 list_del(&type->list);141 142 spin_unlock(&_lock);143 144 return 0;145}146EXPORT_SYMBOL(dm_dirty_log_type_unregister);147 148struct dm_dirty_log *dm_dirty_log_create(const char *type_name,149 struct dm_target *ti,150 int (*flush_callback_fn)(struct dm_target *ti),151 unsigned int argc, char **argv)152{153 struct dm_dirty_log_type *type;154 struct dm_dirty_log *log;155 156 log = kmalloc(sizeof(*log), GFP_KERNEL);157 if (!log)158 return NULL;159 160 type = get_type(type_name);161 if (!type) {162 kfree(log);163 return NULL;164 }165 166 log->flush_callback_fn = flush_callback_fn;167 log->type = type;168 if (type->ctr(log, ti, argc, argv)) {169 kfree(log);170 put_type(type);171 return NULL;172 }173 174 return log;175}176EXPORT_SYMBOL(dm_dirty_log_create);177 178void dm_dirty_log_destroy(struct dm_dirty_log *log)179{180 log->type->dtr(log);181 put_type(log->type);182 kfree(log);183}184EXPORT_SYMBOL(dm_dirty_log_destroy);185 186/*187 *---------------------------------------------------------------188 * Persistent and core logs share a lot of their implementation.189 * FIXME: need a reload method to be called from a resume190 *---------------------------------------------------------------191 */192/*193 * Magic for persistent mirrors: "MiRr"194 */195#define MIRROR_MAGIC 0x4D695272196 197/*198 * The on-disk version of the metadata.199 */200#define MIRROR_DISK_VERSION 2201#define LOG_OFFSET 2202 203struct log_header_disk {204 __le32 magic;205 206 /*207 * Simple, incrementing version. no backward208 * compatibility.209 */210 __le32 version;211 __le64 nr_regions;212} __packed;213 214struct log_header_core {215 uint32_t magic;216 uint32_t version;217 uint64_t nr_regions;218};219 220struct log_c {221 struct dm_target *ti;222 int touched_dirtied;223 int touched_cleaned;224 int flush_failed;225 uint32_t region_size;226 unsigned int region_count;227 region_t sync_count;228 229 unsigned int bitset_uint32_count;230 uint32_t *clean_bits;231 uint32_t *sync_bits;232 uint32_t *recovering_bits; /* FIXME: this seems excessive */233 234 int sync_search;235 236 /* Resync flag */237 enum sync {238 DEFAULTSYNC, /* Synchronize if necessary */239 NOSYNC, /* Devices known to be already in sync */240 FORCESYNC, /* Force a sync to happen */241 } sync;242 243 struct dm_io_request io_req;244 245 /*246 * Disk log fields247 */248 int log_dev_failed;249 int log_dev_flush_failed;250 struct dm_dev *log_dev;251 struct log_header_core header;252 253 struct dm_io_region header_location;254 struct log_header_disk *disk_header;255};256 257/*258 * The touched member needs to be updated every time we access259 * one of the bitsets.260 */261static inline int log_test_bit(uint32_t *bs, unsigned int bit)262{263 return test_bit_le(bit, bs) ? 1 : 0;264}265 266static inline void log_set_bit(struct log_c *l,267 uint32_t *bs, unsigned int bit)268{269 __set_bit_le(bit, bs);270 l->touched_cleaned = 1;271}272 273static inline void log_clear_bit(struct log_c *l,274 uint32_t *bs, unsigned int bit)275{276 __clear_bit_le(bit, bs);277 l->touched_dirtied = 1;278}279 280/*281 *---------------------------------------------------------------282 * Header IO283 *--------------------------------------------------------------284 */285static void header_to_disk(struct log_header_core *core, struct log_header_disk *disk)286{287 disk->magic = cpu_to_le32(core->magic);288 disk->version = cpu_to_le32(core->version);289 disk->nr_regions = cpu_to_le64(core->nr_regions);290}291 292static void header_from_disk(struct log_header_core *core, struct log_header_disk *disk)293{294 core->magic = le32_to_cpu(disk->magic);295 core->version = le32_to_cpu(disk->version);296 core->nr_regions = le64_to_cpu(disk->nr_regions);297}298 299static int rw_header(struct log_c *lc, enum req_op op)300{301 lc->io_req.bi_opf = op;302 303 return dm_io(&lc->io_req, 1, &lc->header_location, NULL, IOPRIO_DEFAULT);304}305 306static int flush_header(struct log_c *lc)307{308 struct dm_io_region null_location = {309 .bdev = lc->header_location.bdev,310 .sector = 0,311 .count = 0,312 };313 314 lc->io_req.bi_opf = REQ_OP_WRITE | REQ_PREFLUSH;315 316 return dm_io(&lc->io_req, 1, &null_location, NULL, IOPRIO_DEFAULT);317}318 319static int read_header(struct log_c *log)320{321 int r;322 323 r = rw_header(log, REQ_OP_READ);324 if (r)325 return r;326 327 header_from_disk(&log->header, log->disk_header);328 329 /* New log required? */330 if (log->sync != DEFAULTSYNC || log->header.magic != MIRROR_MAGIC) {331 log->header.magic = MIRROR_MAGIC;332 log->header.version = MIRROR_DISK_VERSION;333 log->header.nr_regions = 0;334 }335 336#ifdef __LITTLE_ENDIAN337 if (log->header.version == 1)338 log->header.version = 2;339#endif340 341 if (log->header.version != MIRROR_DISK_VERSION) {342 DMWARN("incompatible disk log version");343 return -EINVAL;344 }345 346 return 0;347}348 349static int _check_region_size(struct dm_target *ti, uint32_t region_size)350{351 if (region_size < 2 || region_size > ti->len)352 return 0;353 354 if (!is_power_of_2(region_size))355 return 0;356 357 return 1;358}359 360/*361 *--------------------------------------------------------------362 * core log constructor/destructor363 *364 * argv contains region_size followed optionally by [no]sync365 *--------------------------------------------------------------366 */367#define BYTE_SHIFT 3368static int create_log_context(struct dm_dirty_log *log, struct dm_target *ti,369 unsigned int argc, char **argv,370 struct dm_dev *dev)371{372 enum sync sync = DEFAULTSYNC;373 374 struct log_c *lc;375 uint32_t region_size;376 unsigned int region_count;377 size_t bitset_size, buf_size;378 int r;379 char dummy;380 381 if (argc < 1 || argc > 2) {382 DMWARN("wrong number of arguments to dirty region log");383 return -EINVAL;384 }385 386 if (argc > 1) {387 if (!strcmp(argv[1], "sync"))388 sync = FORCESYNC;389 else if (!strcmp(argv[1], "nosync"))390 sync = NOSYNC;391 else {392 DMWARN("unrecognised sync argument to dirty region log: %s", argv[1]);393 return -EINVAL;394 }395 }396 397 if (sscanf(argv[0], "%u%c", ®ion_size, &dummy) != 1 ||398 !_check_region_size(ti, region_size)) {399 DMWARN("invalid region size %s", argv[0]);400 return -EINVAL;401 }402 403 region_count = dm_sector_div_up(ti->len, region_size);404 405 lc = kmalloc(sizeof(*lc), GFP_KERNEL);406 if (!lc) {407 DMWARN("couldn't allocate core log");408 return -ENOMEM;409 }410 411 lc->ti = ti;412 lc->touched_dirtied = 0;413 lc->touched_cleaned = 0;414 lc->flush_failed = 0;415 lc->region_size = region_size;416 lc->region_count = region_count;417 lc->sync = sync;418 419 /*420 * Work out how many "unsigned long"s we need to hold the bitset.421 */422 bitset_size = dm_round_up(region_count, BITS_PER_LONG);423 bitset_size >>= BYTE_SHIFT;424 425 lc->bitset_uint32_count = bitset_size / sizeof(*lc->clean_bits);426 427 /*428 * Disk log?429 */430 if (!dev) {431 lc->clean_bits = vmalloc(bitset_size);432 if (!lc->clean_bits) {433 DMWARN("couldn't allocate clean bitset");434 kfree(lc);435 return -ENOMEM;436 }437 lc->disk_header = NULL;438 } else {439 lc->log_dev = dev;440 lc->log_dev_failed = 0;441 lc->log_dev_flush_failed = 0;442 lc->header_location.bdev = lc->log_dev->bdev;443 lc->header_location.sector = 0;444 445 /*446 * Buffer holds both header and bitset.447 */448 buf_size =449 dm_round_up((LOG_OFFSET << SECTOR_SHIFT) + bitset_size,450 bdev_logical_block_size(lc->header_location.bdev));451 452 if (buf_size > bdev_nr_bytes(dev->bdev)) {453 DMWARN("log device %s too small: need %llu bytes",454 dev->name, (unsigned long long)buf_size);455 kfree(lc);456 return -EINVAL;457 }458 459 lc->header_location.count = buf_size >> SECTOR_SHIFT;460 461 lc->io_req.mem.type = DM_IO_VMA;462 lc->io_req.notify.fn = NULL;463 lc->io_req.client = dm_io_client_create();464 if (IS_ERR(lc->io_req.client)) {465 r = PTR_ERR(lc->io_req.client);466 DMWARN("couldn't allocate disk io client");467 kfree(lc);468 return r;469 }470 471 lc->disk_header = vmalloc(buf_size);472 if (!lc->disk_header) {473 DMWARN("couldn't allocate disk log buffer");474 dm_io_client_destroy(lc->io_req.client);475 kfree(lc);476 return -ENOMEM;477 }478 479 lc->io_req.mem.ptr.vma = lc->disk_header;480 lc->clean_bits = (void *)lc->disk_header +481 (LOG_OFFSET << SECTOR_SHIFT);482 }483 484 memset(lc->clean_bits, -1, bitset_size);485 486 lc->sync_bits = vmalloc(bitset_size);487 if (!lc->sync_bits) {488 DMWARN("couldn't allocate sync bitset");489 if (!dev)490 vfree(lc->clean_bits);491 else492 dm_io_client_destroy(lc->io_req.client);493 vfree(lc->disk_header);494 kfree(lc);495 return -ENOMEM;496 }497 memset(lc->sync_bits, (sync == NOSYNC) ? -1 : 0, bitset_size);498 lc->sync_count = (sync == NOSYNC) ? region_count : 0;499 500 lc->recovering_bits = vzalloc(bitset_size);501 if (!lc->recovering_bits) {502 DMWARN("couldn't allocate sync bitset");503 vfree(lc->sync_bits);504 if (!dev)505 vfree(lc->clean_bits);506 else507 dm_io_client_destroy(lc->io_req.client);508 vfree(lc->disk_header);509 kfree(lc);510 return -ENOMEM;511 }512 lc->sync_search = 0;513 log->context = lc;514 515 return 0;516}517 518static int core_ctr(struct dm_dirty_log *log, struct dm_target *ti,519 unsigned int argc, char **argv)520{521 return create_log_context(log, ti, argc, argv, NULL);522}523 524static void destroy_log_context(struct log_c *lc)525{526 vfree(lc->sync_bits);527 vfree(lc->recovering_bits);528 kfree(lc);529}530 531static void core_dtr(struct dm_dirty_log *log)532{533 struct log_c *lc = log->context;534 535 vfree(lc->clean_bits);536 destroy_log_context(lc);537}538 539/*540 *---------------------------------------------------------------------541 * disk log constructor/destructor542 *543 * argv contains log_device region_size followed optionally by [no]sync544 *---------------------------------------------------------------------545 */546static int disk_ctr(struct dm_dirty_log *log, struct dm_target *ti,547 unsigned int argc, char **argv)548{549 int r;550 struct dm_dev *dev;551 552 if (argc < 2 || argc > 3) {553 DMWARN("wrong number of arguments to disk dirty region log");554 return -EINVAL;555 }556 557 r = dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &dev);558 if (r)559 return r;560 561 r = create_log_context(log, ti, argc - 1, argv + 1, dev);562 if (r) {563 dm_put_device(ti, dev);564 return r;565 }566 567 return 0;568}569 570static void disk_dtr(struct dm_dirty_log *log)571{572 struct log_c *lc = log->context;573 574 dm_put_device(lc->ti, lc->log_dev);575 vfree(lc->disk_header);576 dm_io_client_destroy(lc->io_req.client);577 destroy_log_context(lc);578}579 580static void fail_log_device(struct log_c *lc)581{582 if (lc->log_dev_failed)583 return;584 585 lc->log_dev_failed = 1;586 dm_table_event(lc->ti->table);587}588 589static int disk_resume(struct dm_dirty_log *log)590{591 int r;592 unsigned int i;593 struct log_c *lc = log->context;594 size_t size = lc->bitset_uint32_count * sizeof(uint32_t);595 596 /* read the disk header */597 r = read_header(lc);598 if (r) {599 DMWARN("%s: Failed to read header on dirty region log device",600 lc->log_dev->name);601 fail_log_device(lc);602 /*603 * If the log device cannot be read, we must assume604 * all regions are out-of-sync. If we simply return605 * here, the state will be uninitialized and could606 * lead us to return 'in-sync' status for regions607 * that are actually 'out-of-sync'.608 */609 lc->header.nr_regions = 0;610 }611 612 /* set or clear any new bits -- device has grown */613 if (lc->sync == NOSYNC)614 for (i = lc->header.nr_regions; i < lc->region_count; i++)615 /* FIXME: amazingly inefficient */616 log_set_bit(lc, lc->clean_bits, i);617 else618 for (i = lc->header.nr_regions; i < lc->region_count; i++)619 /* FIXME: amazingly inefficient */620 log_clear_bit(lc, lc->clean_bits, i);621 622 /* clear any old bits -- device has shrunk */623 for (i = lc->region_count; i % BITS_PER_LONG; i++)624 log_clear_bit(lc, lc->clean_bits, i);625 626 /* copy clean across to sync */627 memcpy(lc->sync_bits, lc->clean_bits, size);628 lc->sync_count = memweight(lc->clean_bits,629 lc->bitset_uint32_count * sizeof(uint32_t));630 lc->sync_search = 0;631 632 /* set the correct number of regions in the header */633 lc->header.nr_regions = lc->region_count;634 635 header_to_disk(&lc->header, lc->disk_header);636 637 /* write the new header */638 r = rw_header(lc, REQ_OP_WRITE);639 if (!r) {640 r = flush_header(lc);641 if (r)642 lc->log_dev_flush_failed = 1;643 }644 if (r) {645 DMWARN("%s: Failed to write header on dirty region log device",646 lc->log_dev->name);647 fail_log_device(lc);648 }649 650 return r;651}652 653static uint32_t core_get_region_size(struct dm_dirty_log *log)654{655 struct log_c *lc = log->context;656 657 return lc->region_size;658}659 660static int core_resume(struct dm_dirty_log *log)661{662 struct log_c *lc = log->context;663 664 lc->sync_search = 0;665 return 0;666}667 668static int core_is_clean(struct dm_dirty_log *log, region_t region)669{670 struct log_c *lc = log->context;671 672 return log_test_bit(lc->clean_bits, region);673}674 675static int core_in_sync(struct dm_dirty_log *log, region_t region, int block)676{677 struct log_c *lc = log->context;678 679 return log_test_bit(lc->sync_bits, region);680}681 682static int core_flush(struct dm_dirty_log *log)683{684 /* no op */685 return 0;686}687 688static int disk_flush(struct dm_dirty_log *log)689{690 int r, i;691 struct log_c *lc = log->context;692 693 /* only write if the log has changed */694 if (!lc->touched_cleaned && !lc->touched_dirtied)695 return 0;696 697 if (lc->touched_cleaned && log->flush_callback_fn &&698 log->flush_callback_fn(lc->ti)) {699 /*700 * At this point it is impossible to determine which701 * regions are clean and which are dirty (without702 * re-reading the log off disk). So mark all of them703 * dirty.704 */705 lc->flush_failed = 1;706 for (i = 0; i < lc->region_count; i++)707 log_clear_bit(lc, lc->clean_bits, i);708 }709 710 r = rw_header(lc, REQ_OP_WRITE);711 if (r)712 fail_log_device(lc);713 else {714 if (lc->touched_dirtied) {715 r = flush_header(lc);716 if (r) {717 lc->log_dev_flush_failed = 1;718 fail_log_device(lc);719 } else720 lc->touched_dirtied = 0;721 }722 lc->touched_cleaned = 0;723 }724 725 return r;726}727 728static void core_mark_region(struct dm_dirty_log *log, region_t region)729{730 struct log_c *lc = log->context;731 732 log_clear_bit(lc, lc->clean_bits, region);733}734 735static void core_clear_region(struct dm_dirty_log *log, region_t region)736{737 struct log_c *lc = log->context;738 739 if (likely(!lc->flush_failed))740 log_set_bit(lc, lc->clean_bits, region);741}742 743static int core_get_resync_work(struct dm_dirty_log *log, region_t *region)744{745 struct log_c *lc = log->context;746 747 if (lc->sync_search >= lc->region_count)748 return 0;749 750 do {751 *region = find_next_zero_bit_le(lc->sync_bits,752 lc->region_count,753 lc->sync_search);754 lc->sync_search = *region + 1;755 756 if (*region >= lc->region_count)757 return 0;758 759 } while (log_test_bit(lc->recovering_bits, *region));760 761 log_set_bit(lc, lc->recovering_bits, *region);762 return 1;763}764 765static void core_set_region_sync(struct dm_dirty_log *log, region_t region,766 int in_sync)767{768 struct log_c *lc = log->context;769 770 log_clear_bit(lc, lc->recovering_bits, region);771 if (in_sync) {772 log_set_bit(lc, lc->sync_bits, region);773 lc->sync_count++;774 } else if (log_test_bit(lc->sync_bits, region)) {775 lc->sync_count--;776 log_clear_bit(lc, lc->sync_bits, region);777 }778}779 780static region_t core_get_sync_count(struct dm_dirty_log *log)781{782 struct log_c *lc = log->context;783 784 return lc->sync_count;785}786 787#define DMEMIT_SYNC \788 do { \789 if (lc->sync != DEFAULTSYNC) \790 DMEMIT("%ssync ", lc->sync == NOSYNC ? "no" : ""); \791 } while (0)792 793static int core_status(struct dm_dirty_log *log, status_type_t status,794 char *result, unsigned int maxlen)795{796 int sz = 0;797 struct log_c *lc = log->context;798 799 switch (status) {800 case STATUSTYPE_INFO:801 DMEMIT("1 %s", log->type->name);802 break;803 804 case STATUSTYPE_TABLE:805 DMEMIT("%s %u %u ", log->type->name,806 lc->sync == DEFAULTSYNC ? 1 : 2, lc->region_size);807 DMEMIT_SYNC;808 break;809 810 case STATUSTYPE_IMA:811 *result = '\0';812 break;813 }814 815 return sz;816}817 818static int disk_status(struct dm_dirty_log *log, status_type_t status,819 char *result, unsigned int maxlen)820{821 int sz = 0;822 struct log_c *lc = log->context;823 824 switch (status) {825 case STATUSTYPE_INFO:826 DMEMIT("3 %s %s %c", log->type->name, lc->log_dev->name,827 lc->log_dev_flush_failed ? 'F' :828 lc->log_dev_failed ? 'D' :829 'A');830 break;831 832 case STATUSTYPE_TABLE:833 DMEMIT("%s %u %s %u ", log->type->name,834 lc->sync == DEFAULTSYNC ? 2 : 3, lc->log_dev->name,835 lc->region_size);836 DMEMIT_SYNC;837 break;838 839 case STATUSTYPE_IMA:840 *result = '\0';841 break;842 }843 844 return sz;845}846 847static struct dm_dirty_log_type _core_type = {848 .name = "core",849 .module = THIS_MODULE,850 .ctr = core_ctr,851 .dtr = core_dtr,852 .resume = core_resume,853 .get_region_size = core_get_region_size,854 .is_clean = core_is_clean,855 .in_sync = core_in_sync,856 .flush = core_flush,857 .mark_region = core_mark_region,858 .clear_region = core_clear_region,859 .get_resync_work = core_get_resync_work,860 .set_region_sync = core_set_region_sync,861 .get_sync_count = core_get_sync_count,862 .status = core_status,863};864 865static struct dm_dirty_log_type _disk_type = {866 .name = "disk",867 .module = THIS_MODULE,868 .ctr = disk_ctr,869 .dtr = disk_dtr,870 .postsuspend = disk_flush,871 .resume = disk_resume,872 .get_region_size = core_get_region_size,873 .is_clean = core_is_clean,874 .in_sync = core_in_sync,875 .flush = disk_flush,876 .mark_region = core_mark_region,877 .clear_region = core_clear_region,878 .get_resync_work = core_get_resync_work,879 .set_region_sync = core_set_region_sync,880 .get_sync_count = core_get_sync_count,881 .status = disk_status,882};883 884static int __init dm_dirty_log_init(void)885{886 int r;887 888 r = dm_dirty_log_type_register(&_core_type);889 if (r)890 DMWARN("couldn't register core log");891 892 r = dm_dirty_log_type_register(&_disk_type);893 if (r) {894 DMWARN("couldn't register disk type");895 dm_dirty_log_type_unregister(&_core_type);896 }897 898 return r;899}900 901static void __exit dm_dirty_log_exit(void)902{903 dm_dirty_log_type_unregister(&_disk_type);904 dm_dirty_log_type_unregister(&_core_type);905}906 907module_init(dm_dirty_log_init);908module_exit(dm_dirty_log_exit);909 910MODULE_DESCRIPTION(DM_NAME " dirty region log");911MODULE_AUTHOR("Joe Thornber, Heinz Mauelshagen <dm-devel@lists.linux.dev>");912MODULE_LICENSE("GPL");913