#include <sys/ctype.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/dtype.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <dev/disk/dm/dm.h>
#include <dev/disk/dm/netbsd-dm.h>
static d_ioctl_t dmioctl;
static d_open_t dmopen;
static d_close_t dmclose;
static d_psize_t dmsize;
static d_strategy_t dmstrategy;
static d_dump_t dmdump;
static int dm_modcmd(module_t mod, int cmd, void *unused);
static int dmdestroy(void);
static void dm_doinit(void);
static int dm_cmd_to_fun(prop_dictionary_t);
static int disk_ioctl_switch(cdev_t, u_long, void *);
static struct dev_ops dmctl_ops = {
{ "dm", 0, D_MPSAFE },
.d_open = dmopen,
.d_close = dmclose,
.d_ioctl = dmioctl,
};
struct dev_ops dm_ops = {
{ "dm", 0, D_DISK | D_MPSAFE },
.d_open = dmopen,
.d_close = dmclose,
.d_read = physread,
.d_write = physwrite,
.d_ioctl = dmioctl,
.d_strategy = dmstrategy,
.d_psize = dmsize,
.d_dump = dmdump,
};
MALLOC_DEFINE(M_DM, "dm", "Device Mapper allocations");
int dm_debug_level = 0;
extern uint64_t dm_dev_counter;
static cdev_t dmcdev;
static moduledata_t dm_mod = {
"dm",
dm_modcmd,
NULL
};
DECLARE_MODULE(dm, dm_mod, SI_SUB_RAID, SI_ORDER_ANY);
MODULE_VERSION(dm, 1);
static struct cmd_function {
const char *cmd;
int (*fn)(prop_dictionary_t);
} cmd_fn[] = {
{.cmd = "version", .fn = NULL},
{.cmd = "targets", .fn = dm_list_versions_ioctl},
{.cmd = "create", .fn = dm_dev_create_ioctl},
{.cmd = "info", .fn = dm_dev_status_ioctl},
{.cmd = "mknodes", .fn = dm_dev_status_ioctl},
{.cmd = "names", .fn = dm_dev_list_ioctl},
{.cmd = "suspend", .fn = dm_dev_suspend_ioctl},
{.cmd = "remove", .fn = dm_dev_remove_ioctl},
{.cmd = "remove_all", .fn = dm_dev_remove_all_ioctl},
{.cmd = "rename", .fn = dm_dev_rename_ioctl},
{.cmd = "resume", .fn = dm_dev_resume_ioctl},
{.cmd = "clear", .fn = dm_table_clear_ioctl},
{.cmd = "deps", .fn = dm_table_deps_ioctl},
{.cmd = "reload", .fn = dm_table_load_ioctl},
{.cmd = "status", .fn = dm_table_status_ioctl},
{.cmd = "table", .fn = dm_table_status_ioctl},
{.cmd = "message", .fn = dm_message_ioctl},
};
static int
dm_modcmd(module_t mod, int cmd, void *unused)
{
int error;
error = 0;
switch (cmd) {
case MOD_LOAD:
dm_doinit();
kprintf("Device Mapper version %d.%d.%d loaded\n",
DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL);
break;
case MOD_UNLOAD:
if (dm_dev_counter > 0)
return EBUSY;
error = dmdestroy();
if (error)
break;
kprintf("Device Mapper unloaded\n");
break;
}
return error;
}
static void
dm_doinit(void)
{
dm_target_init();
dm_dev_init();
dm_pdev_init();
dmcdev = make_dev(&dmctl_ops, 0, UID_ROOT, GID_OPERATOR, 0640, "mapper/control");
}
static int
dmdestroy(void)
{
destroy_dev(dmcdev);
dm_dev_uninit();
dm_pdev_uninit();
dm_target_uninit();
return 0;
}
static int
dmopen(struct dev_open_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
dm_dev_t *dmv;
if (minor(dev) == 0)
return 0;
if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL)
return ENXIO;
dmv->is_open = 1;
dm_dev_unbusy(dmv);
dmdebug("minor=%" PRIu32 "\n", minor(ap->a_head.a_dev));
return 0;
}
static int
dmclose(struct dev_close_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
dm_dev_t *dmv;
if (minor(dev) == 0)
return 0;
if ((dmv = dm_dev_lookup(NULL, NULL, minor(dev))) == NULL)
return ENXIO;
dmv->is_open = 0;
dm_dev_unbusy(dmv);
dmdebug("minor=%" PRIu32 "\n", minor(ap->a_head.a_dev));
return 0;
}
static int
dmioctl(struct dev_ioctl_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
u_long cmd = ap->a_cmd;
void *data = ap->a_data;
struct plistref *pref;
int r, err;
prop_dictionary_t dm_dict_in;
err = r = 0;
KKASSERT(data != NULL);
if ((r = disk_ioctl_switch(dev, cmd, data)) != ENOTTY)
return r;
switch(cmd) {
case NETBSD_DM_IOCTL:
dmdebug("NETBSD_DM_IOCTL called\n");
break;
default:
dmdebug("Unknown ioctl %lu called\n", cmd);
return ENOTTY;
}
pref = (struct plistref *)data;
if ((r = prop_dictionary_copyin_ioctl(pref, cmd, &dm_dict_in)) != 0)
return r;
if ((r = dm_check_version(dm_dict_in)) == 0)
err = dm_cmd_to_fun(dm_dict_in);
r = prop_dictionary_copyout_ioctl(pref, cmd, dm_dict_in);
prop_object_release(dm_dict_in);
if (err)
return err;
return r;
}
static int
dm_cmd_to_fun(prop_dictionary_t dm_dict)
{
int i, size;
prop_string_t command;
struct cmd_function *p = NULL;
size = sizeof(cmd_fn) / sizeof(cmd_fn[0]);
if ((command = prop_dictionary_get(dm_dict, DM_IOCTL_COMMAND)) == NULL)
return EINVAL;
for (i = 0; i < size; i++) {
p = &cmd_fn[i];
if (prop_string_equals_cstring(command, p->cmd))
break;
}
KKASSERT(p);
if (i == size) {
dmdebug("Unknown ioctl\n");
return EINVAL;
}
dmdebug("ioctl %s called %p\n", p->cmd, p->fn);
if (p->fn == NULL)
return 0;
return p->fn(dm_dict);
}
static int
disk_ioctl_switch(cdev_t dev, u_long cmd, void *data)
{
dm_dev_t *dmv;
if (minor(dev) == 0)
return ENOTTY;
switch(cmd) {
case DIOCGPART:
if ((dmv = dev->si_drv1) == NULL)
return ENODEV;
if (dmv->diskp->d_info.d_media_blksize == 0) {
return ENOTSUP;
} else {
u_int64_t size;
struct partinfo *dpart = data;
bzero(dpart, sizeof(*dpart));
size = dm_table_size(&dmv->table_head);
dpart->media_offset = 0;
dpart->media_size = size * DEV_BSIZE;
dpart->media_blocks = size;
dpart->media_blksize = DEV_BSIZE;
dpart->fstype = FS_BSDFFS;
}
dmdebug("DIOCGPART called\n");
break;
default:
dmdebug("Unknown disk ioctl %lu called\n", cmd);
return ENOTTY;
break;
}
return 0;
}
static int
dmstrategy(struct dev_strategy_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
dm_dev_t *dmv = dev->si_drv1;
dm_table_t *tbl;
dm_table_entry_t *table_en;
struct bio *bio = ap->a_bio;
struct buf *bp = bio->bio_buf;
struct buf *nestbuf;
uint64_t buf_start, buf_len, issued_len;
uint64_t table_start, table_end;
uint64_t start, end;
int bypass;
buf_start = bio->bio_offset;
buf_len = bp->b_bcount;
issued_len = 0;
switch(bp->b_cmd) {
case BUF_CMD_READ:
case BUF_CMD_WRITE:
case BUF_CMD_FREEBLKS:
bypass = 0;
break;
case BUF_CMD_FLUSH:
bypass = 1;
KKASSERT(buf_len == 0);
break;
default:
bp->b_error = EIO;
bp->b_resid = bp->b_bcount;
biodone(bio);
return 0;
}
if (bypass == 0 &&
bounds_check_with_mediasize(bio, DEV_BSIZE,
dm_table_size(&dmv->table_head)) <= 0) {
bp->b_resid = bp->b_bcount;
biodone(bio);
return 0;
}
tbl = dm_table_get_entry(&dmv->table_head, DM_TABLE_ACTIVE);
nestiobuf_init(bio);
devstat_start_transaction(&dmv->stats);
TAILQ_FOREACH(table_en, tbl, next) {
table_start = table_en->start * DEV_BSIZE;
table_end = table_start + table_en->length * DEV_BSIZE;
start = MAX(table_start, buf_start);
end = MIN(table_end, buf_start + buf_len);
if (dm_debug_level) {
kprintf("----------------------------------------\n");
kprintf("table_start %010" PRIu64", table_end %010"
PRIu64 "\n", table_start, table_end);
kprintf("buf_start %010" PRIu64", buf_len %010"
PRIu64"\n", buf_start, buf_len);
kprintf("start-buf_start %010"PRIu64", end %010"
PRIu64"\n", start - buf_start, end);
kprintf("start %010" PRIu64", end %010"
PRIu64"\n", start, end);
}
if (bypass) {
nestbuf = getpbuf(NULL);
nestbuf->b_flags |= bio->bio_buf->b_flags & B_HASBOGUS;
nestiobuf_add(bio, nestbuf, 0, 0, &dmv->stats);
nestbuf->b_bio1.bio_offset = 0;
table_en->target->strategy(table_en, nestbuf);
} else if (start < end) {
nestbuf = getpbuf(NULL);
nestbuf->b_flags |= bio->bio_buf->b_flags & B_HASBOGUS;
nestiobuf_add(bio, nestbuf,
start - buf_start, end - start,
&dmv->stats);
issued_len += end - start;
nestbuf->b_bio1.bio_offset = start - table_start;
table_en->target->strategy(table_en, nestbuf);
}
}
if (issued_len < buf_len)
nestiobuf_error(bio, EINVAL);
nestiobuf_start(bio);
dm_table_release(&dmv->table_head, DM_TABLE_ACTIVE);
return 0;
}
static int
dmdump(struct dev_dump_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
dm_dev_t *dmv = dev->si_drv1;
dm_table_t *tbl;
dm_table_entry_t *table_en;
uint64_t buf_start, buf_len, issued_len;
uint64_t table_start, table_end;
uint64_t start, end;
int error = 0;
buf_start = ap->a_offset;
buf_len = ap->a_length;
issued_len = 0;
tbl = dm_table_get_entry(&dmv->table_head, DM_TABLE_ACTIVE);
TAILQ_FOREACH(table_en, tbl, next) {
table_start = table_en->start * DEV_BSIZE;
table_end = table_start + table_en->length * DEV_BSIZE;
start = MAX(table_start, buf_start);
end = MIN(table_end, buf_start + buf_len);
if (ap->a_length == 0) {
if (table_en->target->dump == NULL) {
error = ENXIO;
goto out;
}
table_en->target->dump(table_en, NULL, 0, 0);
} else if (start < end) {
if (table_en->target->dump == NULL) {
error = ENXIO;
goto out;
}
table_en->target->dump(table_en,
(char *)ap->a_virtual + start - buf_start,
end - start, start - table_start);
issued_len += end - start;
}
}
if (issued_len < buf_len)
error = EINVAL;
out:
dm_table_release(&dmv->table_head, DM_TABLE_ACTIVE);
return error;
}
static int
dmsize(struct dev_psize_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
dm_dev_t *dmv;
if ((dmv = dev->si_drv1) == NULL)
return ENXIO;
ap->a_result = (int64_t)dm_table_size(&dmv->table_head);
return 0;
}
void
dmsetdiskinfo(struct disk *disk, dm_table_head_t *head)
{
struct disk_info info;
uint64_t dmp_size;
dmp_size = dm_table_size(head);
bzero(&info, sizeof(struct disk_info));
info.d_media_blksize = DEV_BSIZE;
info.d_media_blocks = dmp_size;
#if 0
info.d_media_size = dmp_size * DEV_BSIZE;
#endif
info.d_dsflags = DSO_MBRQUIET | DSO_DEVICEMAPPER | DSO_RAWPSIZE;
info.d_secpertrack = 32;
info.d_nheads = 64;
info.d_secpercyl = info.d_secpertrack * info.d_nheads;
info.d_ncylinders = dmp_size / info.d_secpercyl;
disk_setdiskinfo(disk, &info);
disk_config(NULL);
}
uint64_t
atoi64(const char *s)
{
uint64_t n;
n = 0;
while (*s != '\0') {
if (!isdigit(*s))
break;
n = (10 * n) + (*s - '0');
s++;
}
return n;
}
char *
dm_alloc_string(int len)
{
if (len <= 0)
len = DM_MAX_PARAMS_SIZE;
return kmalloc(len, M_DM, M_WAITOK | M_ZERO);
}
void
dm_builtin_init(void *arg)
{
modeventhand_t evh = (modeventhand_t)arg;
KKASSERT(evh != NULL);
evh(NULL, MOD_LOAD, NULL);
}
void
dm_builtin_uninit(void *arg)
{
modeventhand_t evh = (modeventhand_t)arg;
KKASSERT(evh != NULL);
evh(NULL, MOD_UNLOAD, NULL);
}
TUNABLE_INT("debug.dm_debug", &dm_debug_level);
SYSCTL_INT(_debug, OID_AUTO, dm_debug, CTLFLAG_RW, &dm_debug_level,
0, "Enable device mapper debugging");