/* * Copyright (C) 2010-2012 by Dell Inc. All rights reserved. * Copyright (C) 2011-2013 Red Hat, Inc. * * This file is released under the GPL. * * dm-switch is a device-mapper target that maps IO to underlying block * devices efficiently when there are a large number of fixed-sized * address regions but there is no simple pattern to allow for a compact * mapping representation such as dm-stripe. */ #include <linux/device-mapper.h> #include <linux/module.h> #include <linux/init.h> #include <linux/vmalloc.h> #define DM_MSG_PREFIX "switch" /* * One region_table_slot_t holds <region_entries_per_slot> region table * entries each of which is <region_table_entry_bits> in size. */ typedef unsigned long region_table_slot_t; /* * A device with the offset to its start sector. */ struct switch_path { struct dm_dev *dmdev; sector_t start; }; /* * Context block for a dm switch device. */ struct switch_ctx { struct dm_target *ti; unsigned nr_paths; /* Number of paths in path_list. */ unsigned region_size; /* Region size in 512-byte sectors */ unsigned long nr_regions; /* Number of regions making up the device */ signed char region_size_bits; /* log2 of region_size or -1 */ unsigned char region_table_entry_bits; /* Number of bits in one region table entry */ unsigned char region_entries_per_slot; /* Number of entries in one region table slot */ signed char region_entries_per_slot_bits; /* log2 of region_entries_per_slot or -1 */ region_table_slot_t *region_table; /* Region table */ /* * Array of dm devices to switch between. */ struct switch_path path_list[0]; }; static struct switch_ctx *alloc_switch_ctx(struct dm_target *ti, unsigned nr_paths, unsigned region_size) { struct switch_ctx *sctx; sctx = kzalloc(sizeof(struct switch_ctx) + nr_paths * sizeof(struct switch_path), GFP_KERNEL); if (!sctx) return NULL; sctx->ti = ti; sctx->region_size = region_size; ti->private = sctx; return sctx; } static int alloc_region_table(struct dm_target *ti, unsigned nr_paths) { struct switch_ctx *sctx = ti->private; sector_t nr_regions = ti->len; sector_t nr_slots; if (!(sctx->region_size & (sctx->region_size - 1))) sctx->region_size_bits = __ffs(sctx->region_size); else sctx->region_size_bits = -1; sctx->region_table_entry_bits = 1; while (sctx->region_table_entry_bits < sizeof(region_table_slot_t) * 8 && (region_table_slot_t)1 << sctx->region_table_entry_bits < nr_paths) sctx->region_table_entry_bits++; sctx->region_entries_per_slot = (sizeof(region_table_slot_t) * 8) / sctx->region_table_entry_bits; if (!(sctx->region_entries_per_slot & (sctx->region_entries_per_slot - 1))) sctx->region_entries_per_slot_bits = __ffs(sctx->region_entries_per_slot); else sctx->region_entries_per_slot_bits = -1; if (sector_div(nr_regions, sctx->region_size)) nr_regions++; if (nr_regions >= ULONG_MAX) { ti->error = "Region table too large"; return -EINVAL; } sctx->nr_regions = nr_regions; nr_slots = nr_regions; if (sector_div(nr_slots, sctx->region_entries_per_slot)) nr_slots++; if (nr_slots > ULONG_MAX / sizeof(region_table_slot_t)) { ti->error = "Region table too large"; return -EINVAL; } sctx->region_table = vmalloc(nr_slots * sizeof(region_table_slot_t)); if (!sctx->region_table) { ti->error = "Cannot allocate region table"; return -ENOMEM; } return 0; } static void switch_get_position(struct switch_ctx *sctx, unsigned long region_nr, unsigned long *region_index, unsigned *bit) { if (sctx->region_entries_per_slot_bits >= 0) { *region_index = region_nr >> sctx->region_entries_per_slot_bits; *bit = region_nr & (sctx->region_entries_per_slot - 1); } else { *region_index = region_nr / sctx->region_entries_per_slot; *bit = region_nr % sctx->region_entries_per_slot; } *bit *= sctx->region_table_entry_bits; } static unsigned switch_region_table_read(struct switch_ctx *sctx, unsigned long region_nr) { unsigned long region_index; unsigned bit; switch_get_position(sctx, region_nr, ®ion_index, &bit); return (ACCESS_ONCE(sctx->region_table[region_index]) >> bit) & ((1 << sctx->region_table_entry_bits) - 1); } /* * Find which path to use at given offset. */ static unsigned switch_get_path_nr(struct switch_ctx *sctx, sector_t offset) { unsigned path_nr; sector_t p; p = offset; if (sctx->region_size_bits >= 0) p >>= sctx->region_size_bits; else sector_div(p, sctx->region_size); path_nr = switch_region_table_read(sctx, p); /* This can only happen if the processor uses non-atomic stores. */ if (unlikely(path_nr >= sctx->nr_paths)) path_nr = 0; return path_nr; } static void switch_region_table_write(struct switch_ctx *sctx, unsigned long region_nr, unsigned value) { unsigned long region_index; unsigned bit; region_table_slot_t pte; switch_get_position(sctx, region_nr, ®ion_index, &bit); pte = sctx->region_table[region_index]; pte &= ~((((region_table_slot_t)1 << sctx->region_table_entry_bits) - 1) << bit); pte |= (region_table_slot_t)value << bit; sctx->region_table[region_index] = pte; } /* * Fill the region table with an initial round robin pattern. */ static void initialise_region_table(struct switch_ctx *sctx) { unsigned path_nr = 0; unsigned long region_nr; for (region_nr = 0; region_nr < sctx->nr_regions; region_nr++) { switch_region_table_write(sctx, region_nr, path_nr); if (++path_nr >= sctx->nr_paths) path_nr = 0; } } static int parse_path(struct dm_arg_set *as, struct dm_target *ti) { struct switch_ctx *sctx = ti->private; unsigned long long start; int r; r = dm_get_device(ti, dm_shift_arg(as), dm_table_get_mode(ti->table), &sctx->path_list[sctx->nr_paths].dmdev); if (r) { ti->error = "Device lookup failed"; return r; } if (kstrtoull(dm_shift_arg(as), 10, &start) || start != (sector_t)start) { ti->error = "Invalid device starting offset"; dm_put_device(ti, sctx->path_list[sctx->nr_paths].dmdev); return -EINVAL; } sctx->path_list[sctx->nr_paths].start = start; sctx->nr_paths++; return 0; } /* * Destructor: Don't free the dm_target, just the ti->private data (if any). */ static void switch_dtr(struct dm_target *ti) { struct switch_ctx *sctx = ti->private; while (sctx->nr_paths--) dm_put_device(ti, sctx->path_list[sctx->nr_paths].dmdev); vfree(sctx->region_table); kfree(sctx); } /* * Constructor arguments: * <num_paths> <region_size> <num_optional_args> [<optional_args>...] * [<dev_path> <offset>]+ * * Optional args are to allow for future extension: currently this * parameter must be 0. */ static int switch_ctr(struct dm_target *ti, unsigned argc, char **argv) { static struct dm_arg _args[] = { {1, (KMALLOC_MAX_SIZE - sizeof(struct switch_ctx)) / sizeof(struct switch_path), "Invalid number of paths"}, {1, UINT_MAX, "Invalid region size"}, {0, 0, "Invalid number of optional args"}, }; struct switch_ctx *sctx; struct dm_arg_set as; unsigned nr_paths, region_size, nr_optional_args; int r; as.argc = argc; as.argv = argv; r = dm_read_arg(_args, &as, &nr_paths, &ti->error); if (r) return -EINVAL; r = dm_read_arg(_args + 1, &as, ®ion_size, &ti->error); if (r) return r; r = dm_read_arg_group(_args + 2, &as, &nr_optional_args, &ti->error); if (r) return r; /* parse optional arguments here, if we add any */ if (as.argc != nr_paths * 2) { ti->error = "Incorrect number of path arguments"; return -EINVAL; } sctx = alloc_switch_ctx(ti, nr_paths, region_size); if (!sctx) { ti->error = "Cannot allocate redirection context"; return -ENOMEM; } r = dm_set_target_max_io_len(ti, region_size); if (r) goto error; while (as.argc) { r = parse_path(&as, ti); if (r) goto error; } r = alloc_region_table(ti, nr_paths); if (r) goto error; initialise_region_table(sctx); /* For UNMAP, sending the request down any path is sufficient */ ti->num_discard_bios = 1; return 0; error: switch_dtr(ti); return r; } static int switch_map(struct dm_target *ti, struct bio *bio) { struct switch_ctx *sctx = ti->private; sector_t offset = dm_target_offset(ti, bio->bi_iter.bi_sector); unsigned path_nr = switch_get_path_nr(sctx, offset); bio->bi_bdev = sctx->path_list[path_nr].dmdev->bdev; bio->bi_iter.bi_sector = sctx->path_list[path_nr].start + offset; return DM_MAPIO_REMAPPED; } /* * We need to parse hex numbers in the message as quickly as possible. * * This table-based hex parser improves performance. * It improves a time to load 1000000 entries compared to the condition-based * parser. * table-based parser condition-based parser * PA-RISC 0.29s 0.31s * Opteron 0.0495s 0.0498s */ static const unsigned char hex_table[256] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; static __always_inline unsigned long parse_hex(const char **string) { unsigned char d; unsigned long r = 0; while ((d = hex_table[(unsigned char)**string]) < 16) { r = (r << 4) | d; (*string)++; } return r; } static int process_set_region_mappings(struct switch_ctx *sctx, unsigned argc, char **argv) { unsigned i; unsigned long region_index = 0; for (i = 1; i < argc; i++) { unsigned long path_nr; const char *string = argv[i]; if ((*string & 0xdf) == 'R') { unsigned long cycle_length, num_write; string++; if (unlikely(*string == ',')) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } cycle_length = parse_hex(&string); if (unlikely(*string != ',')) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } string++; if (unlikely(!*string)) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } num_write = parse_hex(&string); if (unlikely(*string)) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } if (unlikely(!cycle_length) || unlikely(cycle_length - 1 > region_index)) { DMWARN("invalid set_region_mappings cycle length: %lu > %lu", cycle_length - 1, region_index); return -EINVAL; } if (unlikely(region_index + num_write < region_index) || unlikely(region_index + num_write >= sctx->nr_regions)) { DMWARN("invalid set_region_mappings region number: %lu + %lu >= %lu", region_index, num_write, sctx->nr_regions); return -EINVAL; } while (num_write--) { region_index++; path_nr = switch_region_table_read(sctx, region_index - cycle_length); switch_region_table_write(sctx, region_index, path_nr); } continue; } if (*string == ':') region_index++; else { region_index = parse_hex(&string); if (unlikely(*string != ':')) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } } string++; if (unlikely(!*string)) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } path_nr = parse_hex(&string); if (unlikely(*string)) { DMWARN("invalid set_region_mappings argument: '%s'", argv[i]); return -EINVAL; } if (unlikely(region_index >= sctx->nr_regions)) { DMWARN("invalid set_region_mappings region number: %lu >= %lu", region_index, sctx->nr_regions); return -EINVAL; } if (unlikely(path_nr >= sctx->nr_paths)) { DMWARN("invalid set_region_mappings device: %lu >= %u", path_nr, sctx->nr_paths); return -EINVAL; } switch_region_table_write(sctx, region_index, path_nr); } return 0; } /* * Messages are processed one-at-a-time. * * Only set_region_mappings is supported. */ static int switch_message(struct dm_target *ti, unsigned argc, char **argv) { static DEFINE_MUTEX(message_mutex); struct switch_ctx *sctx = ti->private; int r = -EINVAL; mutex_lock(&message_mutex); if (!strcasecmp(argv[0], "set_region_mappings")) r = process_set_region_mappings(sctx, argc, argv); else DMWARN("Unrecognised message received."); mutex_unlock(&message_mutex); return r; } static void switch_status(struct dm_target *ti, status_type_t type, unsigned status_flags, char *result, unsigned maxlen) { struct switch_ctx *sctx = ti->private; unsigned sz = 0; int path_nr; switch (type) { case STATUSTYPE_INFO: result[0] = '\0'; break; case STATUSTYPE_TABLE: DMEMIT("%u %u 0", sctx->nr_paths, sctx->region_size); for (path_nr = 0; path_nr < sctx->nr_paths; path_nr++) DMEMIT(" %s %llu", sctx->path_list[path_nr].dmdev->name, (unsigned long long)sctx->path_list[path_nr].start); break; } } /* * Switch ioctl: * * Passthrough all ioctls to the path for sector 0 */ static int switch_prepare_ioctl(struct dm_target *ti, struct block_device **bdev, fmode_t *mode) { struct switch_ctx *sctx = ti->private; unsigned path_nr; path_nr = switch_get_path_nr(sctx, 0); *bdev = sctx->path_list[path_nr].dmdev->bdev; *mode = sctx->path_list[path_nr].dmdev->mode; /* * Only pass ioctls through if the device sizes match exactly. */ if (ti->len + sctx->path_list[path_nr].start != i_size_read((*bdev)->bd_inode) >> SECTOR_SHIFT) return 1; return 0; } static int switch_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) { struct switch_ctx *sctx = ti->private; int path_nr; int r; for (path_nr = 0; path_nr < sctx->nr_paths; path_nr++) { r = fn(ti, sctx->path_list[path_nr].dmdev, sctx->path_list[path_nr].start, ti->len, data); if (r) return r; } return 0; } static struct target_type switch_target = { .name = "switch", .version = {1, 1, 0}, .module = THIS_MODULE, .ctr = switch_ctr, .dtr = switch_dtr, .map = switch_map, .message = switch_message, .status = switch_status, .prepare_ioctl = switch_prepare_ioctl, .iterate_devices = switch_iterate_devices, }; static int __init dm_switch_init(void) { int r; r = dm_register_target(&switch_target); if (r < 0) DMERR("dm_register_target() failed %d", r); return r; } static void __exit dm_switch_exit(void) { dm_unregister_target(&switch_target); } module_init(dm_switch_init); module_exit(dm_switch_exit); MODULE_DESCRIPTION(DM_NAME " dynamic path switching target"); MODULE_AUTHOR("Kevin D. O'Kelley <Kevin_OKelley@dell.com>"); MODULE_AUTHOR("Narendran Ganapathy <Narendran_Ganapathy@dell.com>"); MODULE_AUTHOR("Jim Ramsay <Jim_Ramsay@dell.com>"); MODULE_AUTHOR("Mikulas Patocka <mpatocka@redhat.com>"); MODULE_LICENSE("GPL");