Add optional read/write_delay parameters to dm-rwsplit which cause reads or writes to be delayed by a specified number of milliseconds. Index: linux-2.6.16.i686/drivers/md/dm-rwsplit.c =================================================================== --- linux-2.6.16.i686.orig/drivers/md/dm-rwsplit.c 2006-04-24 09:22:58.000000000 +0100 +++ linux-2.6.16.i686/drivers/md/dm-rwsplit.c 2006-04-27 16:41:18.000000000 +0100 @@ -22,18 +22,20 @@ struct split_c { struct dm_dev *dev_write; sector_t start_read; sector_t start_write; + unsigned read_delay; + unsigned write_delay; }; /* * Construct a split mapping: * - * + * [read_delay] [write_delay] */ static int rwsplit_ctr(struct dm_target *ti, unsigned int argc, char **argv) { struct split_c *sc; - if (argc != 4) { + if (argc < 4 || argc > 6) { ti->error = "dm-rwsplit: Invalid argument count"; return -EINVAL; } @@ -49,11 +51,23 @@ static int rwsplit_ctr(struct dm_target goto bad; } + sc->read_delay = 0; + if (argc >= 5 && sscanf(argv[4], "%u", &sc->read_delay) != 1) { + ti->error = "dm-rwsplit: Invalid read delay"; + goto bad; + } + if (sscanf(argv[3], "%llu", &sc->start_write) != 1) { ti->error = "dm-rwsplit: Invalid write device sector"; goto bad; } + sc->write_delay = 0; + if (argc >= 6 && sscanf(argv[5], "%u", &sc->write_delay) != 1) { + ti->error = "dm-rwsplit: Invalid write delay"; + goto bad; + } + if (dm_get_device(ti, argv[0], sc->start_read, ti->len, dm_table_get_mode(ti->table), &sc->dev_read)) { ti->error = "dm-rwsplit: Read device lookup failed"; @@ -84,6 +98,102 @@ static void rwsplit_dtr(struct dm_target kfree(sc); } +struct delayed_bio { + struct list_head list; + struct bio *bio; + unsigned long expires; +}; + +static struct workqueue_struct *kdelayd_wq; +static struct work_struct kdelayd_work; + +static DECLARE_MUTEX(delayed_bios_lock); +static LIST_HEAD(delayed_bios); +struct timer_list delayed_bios_timer; + +static kmem_cache_t *delayed_cache; +static mempool_t *delayed_pool; + +static void handle_delayed_timer(unsigned long data) +{ + queue_work(kdelayd_wq, &kdelayd_work); +} + +static inline void queue_timeout(unsigned long expires) +{ + init_timer(&delayed_bios_timer); + delayed_bios_timer.expires = expires; + delayed_bios_timer.data = 0; + delayed_bios_timer.function = handle_delayed_timer; + add_timer(&delayed_bios_timer); +} + +static void flush_bios(int flush_all) +{ + struct delayed_bio *delayed; + struct delayed_bio *next; + unsigned long next_expires = 0; + int start_timer = 0; + + list_for_each_entry_safe (delayed, next, &delayed_bios, list) { + if (flush_all || time_after_eq(jiffies, delayed->expires)) { + generic_make_request(delayed->bio); + list_del(&delayed->list); + mempool_free(delayed, delayed_pool); + } else if (!start_timer) { + start_timer = 1; + next_expires = delayed->expires; + } else + next_expires = min(next_expires, delayed->expires); + } + + if (start_timer) + queue_timeout(next_expires); +} + +static void flush_expired_bios(void *data) +{ + down(&delayed_bios_lock); + + flush_bios(0); + + up(&delayed_bios_lock); +} + +static int delay_bio(struct split_c *sc, int delay, struct bio *bio) +{ + struct delayed_bio *delayed; + + if (delay == 0) + return 1; + + delayed = mempool_alloc(delayed_pool, GFP_NOIO); + + delayed->bio = bio; + delayed->expires = jiffies + (delay * HZ / 1000); + + down(&delayed_bios_lock); + + if (list_empty(&delayed_bios)) + queue_timeout(delayed->expires); + + list_add_tail(&delayed->list, &delayed_bios); + + up(&delayed_bios_lock); + + return 0; +} + +static void rwsplit_postsuspend(struct dm_target *ti) +{ + down(&delayed_bios_lock); + + del_timer_sync(&delayed_bios_timer); + flush_bios(1); + + up(&delayed_bios_lock); +} + static int rwsplit_map(struct dm_target *ti, struct bio *bio, union map_info *map_context) { @@ -93,13 +203,15 @@ static int rwsplit_map(struct dm_target bio->bi_bdev = sc->dev_write->bdev; bio->bi_sector = sc->start_write + (bio->bi_sector - ti->begin);; + + return delay_bio(sc, sc->write_delay, bio); } else { bio->bi_bdev = sc->dev_read->bdev; bio->bi_sector = sc->start_read + (bio->bi_sector - ti->begin);; - } - return 1; + return delay_bio(sc, sc->read_delay, bio); + } } static int rwsplit_status(struct dm_target *ti, status_type_t type, @@ -113,9 +225,10 @@ static int rwsplit_status(struct dm_targ break; case STATUSTYPE_TABLE: - snprintf(result, maxlen, "%s %llu %s %llu", + snprintf(result, maxlen, "%s %llu %s %llu %u %u", sc->dev_read->name, sc->start_read, - sc->dev_write->name, sc->start_write); + sc->dev_write->name, sc->start_write, + sc->read_delay, sc->write_delay); break; } @@ -123,22 +236,63 @@ static int rwsplit_status(struct dm_targ } static struct target_type rwsplit_target = { - .name = "rwsplit", - .version= {1, 0, 1}, - .module = THIS_MODULE, - .ctr = rwsplit_ctr, - .dtr = rwsplit_dtr, - .map = rwsplit_map, - .status = rwsplit_status, + .name = "rwsplit", + .version = {1, 0, 1}, + .module = THIS_MODULE, + .ctr = rwsplit_ctr, + .dtr = rwsplit_dtr, + .map = rwsplit_map, + .postsuspend = rwsplit_postsuspend, + .status = rwsplit_status, }; int __init dm_rwsplit_init(void) { - int r = dm_register_target(&rwsplit_target); + int r; - if (r < 0) + init_timer(&delayed_bios_timer); + + kdelayd_wq = create_singlethread_workqueue("kdelayd"); + if (!kdelayd_wq) { + DMERR("couldn't start kdelayd"); + r = -ENOMEM; + goto bad1; + } + INIT_WORK(&kdelayd_work, flush_expired_bios, NULL); + + delayed_cache = kmem_cache_create("dm-rwsplit-delayed", + sizeof(struct delayed_bio), + __alignof__(struct delayed_bio), + 0, NULL, NULL); + if (!delayed_cache) { + DMERR("Couldn't create pending cache."); + r = -ENOMEM; + goto bad2; + } + + delayed_pool = mempool_create_slab_pool(128, delayed_cache); + if (!delayed_pool) { + DMERR("Couldn't create pending pool."); + r = -ENOMEM; + goto bad3; + } + + + r = dm_register_target(&rwsplit_target); + if (r < 0) { DMERR("rwsplit: register failed %d", r); + goto bad4; + } + return 0; + + bad4: + mempool_destroy(delayed_pool); + bad3: + kmem_cache_destroy(delayed_cache); + bad2: + destroy_workqueue(kdelayd_wq); + bad1: return r; } @@ -148,6 +302,10 @@ void dm_rwsplit_exit(void) if (r < 0) DMERR("rwsplit: unregister failed %d", r); + + mempool_destroy(delayed_pool); + kmem_cache_destroy(delayed_cache); + destroy_workqueue(kdelayd_wq); } /* Module hooks */