diff options
-rw-r--r-- | drivers/block/loop/loop_file_fmt_qcow_main.c | 295 | ||||
-rw-r--r-- | drivers/block/loop/loop_file_fmt_qcow_main.h | 25 | ||||
-rw-r--r-- | drivers/block/loop/loop_main.c | 30 | ||||
-rw-r--r-- | drivers/block/loop/loop_main.h | 8 |
4 files changed, 291 insertions, 67 deletions
diff --git a/drivers/block/loop/loop_file_fmt_qcow_main.c b/drivers/block/loop/loop_file_fmt_qcow_main.c index 3b40d90d0a1e..70436c8fc076 100644 --- a/drivers/block/loop/loop_file_fmt_qcow_main.c +++ b/drivers/block/loop/loop_file_fmt_qcow_main.c @@ -16,6 +16,7 @@ #include <linux/blkdev.h> #include <linux/bio.h> #include <linux/bvec.h> +#include <linux/mutex.h> #include <linux/uio.h> #include <linux/string.h> #include <linux/vmalloc.h> @@ -100,70 +101,6 @@ static int __qcow_file_fmt_header_read(struct loop_file_fmt *lo_fmt, return ret; } -#ifdef CONFIG_DEBUG_DRIVER -static void __qcow_file_fmt_header_print( - struct loop_file_fmt_qcow_header *header) -{ - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.magic=%d", - header->magic); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.version=%d", - header->version); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.backing_file_offset=%lld", - header->backing_file_offset); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.backing_file_size=%d", - header->backing_file_size); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.cluster_bits=%d", - header->cluster_bits); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.size=%lld", - header->size); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.crypt_method=%d", - header->crypt_method); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.l1_size=%d", - header->l1_size); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.l1_table_offset=%lld", - header->l1_table_offset); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.refcount_table_offset=%lld", - header->refcount_table_offset); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.refcount_table_clusters=%d", - header->refcount_table_clusters); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.nb_snapshots=%d", - header->nb_snapshots); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.snapshots_offset=%lld", - header->snapshots_offset); - - if (header->version == 3) { - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.incompatible_features=%lld", - header->incompatible_features); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.compatible_features=%lld", - header->compatible_features); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.autoclear_features=%lld", - header->autoclear_features); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.refcount_order=%d", - header->refcount_order); - printk(KERN_DEBUG "loop_file_fmt_qcow: " - "header.header_length=%d", - header->header_length); - } -} -#endif - static int __qcow_file_fmt_validate_table(struct loop_file_fmt *lo_fmt, u64 offset, u64 entries, size_t entry_len, s64 max_size_bytes, const char *table_name) @@ -242,6 +179,217 @@ static void __qcow_file_fmt_compression_exit(struct loop_file_fmt *lo_fmt) kfree(qcow_data->strm); } +#ifdef CONFIG_DEBUG_FS +static void __qcow_file_fmt_header_to_buf(struct loop_file_fmt* lo_fmt, + const struct loop_file_fmt_qcow_header *header) +{ + struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; + char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; + ssize_t len = 0; + + len += sprintf(header_buf + len, "magic: %d\n", + header->magic); + len += sprintf(header_buf + len, "version: %d\n", + header->version); + len += sprintf(header_buf + len, "backing_file_offset: %lld\n", + header->backing_file_offset); + len += sprintf(header_buf + len, "backing_file_size: %d\n", + header->backing_file_size); + len += sprintf(header_buf + len, "cluster_bits: %d\n", + header->cluster_bits); + len += sprintf(header_buf + len, "size: %lld\n", + header->size); + len += sprintf(header_buf + len, "crypt_method: %d\n", + header->crypt_method); + len += sprintf(header_buf + len, "l1_size: %d\n", + header->l1_size); + len += sprintf(header_buf + len, "l1_table_offset: %lld\n", + header->l1_table_offset); + len += sprintf(header_buf + len, "refcount_table_offset: %lld\n", + header->refcount_table_offset); + len += sprintf(header_buf + len, "refcount_table_clusters: %d\n", + header->refcount_table_clusters); + len += sprintf(header_buf + len, "nb_snapshots: %d\n", + header->nb_snapshots); + len += sprintf(header_buf + len, "snapshots_offset: %lld\n", + header->snapshots_offset); + + if (header->version == 3) { + len += sprintf(header_buf + len, + "incompatible_features: %lld\n", + header->incompatible_features); + len += sprintf(header_buf + len, + "compatible_features: %lld\n", + header->compatible_features); + len += sprintf(header_buf + len, + "autoclear_features: %lld\n", + header->autoclear_features); + len += sprintf(header_buf + len, + "refcount_order: %d\n", + header->refcount_order); + len += sprintf(header_buf + len, + "header_length: %d\n", + header->header_length); + } + + ASSERT(len < QCOW_HEADER_BUF_LEN); +} + +static ssize_t __qcow_file_fmt_dbgfs_hdr_read(struct file *file, + char __user *buf, size_t size, loff_t *ppos) +{ + struct loop_file_fmt *lo_fmt = file->private_data; + struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; + char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; + + return simple_read_from_buffer(buf, size, ppos, header_buf, + strlen(header_buf)); +} + +static const struct file_operations qcow_file_fmt_dbgfs_hdr_fops = { + .open = simple_open, + .read = __qcow_file_fmt_dbgfs_hdr_read +}; + +static ssize_t __qcow_file_fmt_dbgfs_ofs_read(struct file *file, + char __user *buf, size_t size, loff_t *ppos) +{ + struct loop_file_fmt *lo_fmt = file->private_data; + struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; + unsigned int cur_bytes = 1; + u64 offset = 0; + u64 cluster_offset = 0; + s64 offset_in_cluster = 0; + ssize_t len = 0; + int ret = 0; + + /* read the share debugfs offset */ + ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); + if (ret) + return ret; + + offset = qcow_data->dbgfs_qcow_offset; + mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); + + /* calculate and print the cluster offset */ + ret = loop_file_fmt_qcow_cluster_get_offset(lo_fmt, + offset, &cur_bytes, &cluster_offset); + if (ret < 0) + return -EINVAL; + + offset_in_cluster = loop_file_fmt_qcow_offset_into_cluster(qcow_data, + offset); + + len = sprintf(qcow_data->dbgfs_file_qcow_cluster_buf, + "offset: %lld\ncluster_offset: %lld\noffset_in_cluster: %lld\n", + offset, cluster_offset, offset_in_cluster); + + ASSERT(len < QCOW_CLUSTER_BUF_LEN); + + return simple_read_from_buffer(buf, size, ppos, + qcow_data->dbgfs_file_qcow_cluster_buf, len); +} + +static ssize_t __qcow_file_fmt_dbgfs_ofs_write(struct file *file, + const char __user *buf, size_t size, loff_t *ppos) +{ + struct loop_file_fmt *lo_fmt = file->private_data; + struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; + ssize_t len = 0; + int ret = 0; + + if (*ppos > QCOW_OFFSET_BUF_LEN || size > QCOW_OFFSET_BUF_LEN) + return -EINVAL; + + len = simple_write_to_buffer(qcow_data->dbgfs_file_qcow_offset_buf, + QCOW_OFFSET_BUF_LEN, ppos, buf, size); + if (len < 0) + return len; + + qcow_data->dbgfs_file_qcow_offset_buf[len] = '\0'; + + ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); + if (ret) + return ret; + + ret = kstrtou64(qcow_data->dbgfs_file_qcow_offset_buf, 10, + &qcow_data->dbgfs_qcow_offset); + if (ret < 0) + goto out; + + ret = len; +out: + mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); + return ret; +} + +static const struct file_operations qcow_file_fmt_dbgfs_ofs_fops = { + .open = simple_open, + .read = __qcow_file_fmt_dbgfs_ofs_read, + .write = __qcow_file_fmt_dbgfs_ofs_write +}; + +static int __qcow_file_fmt_dbgfs_init(struct loop_file_fmt *lo_fmt) +{ + struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; + struct loop_device *lo = loop_file_fmt_get_lo(lo_fmt); + int ret = 0; + + qcow_data->dbgfs_dir = debugfs_create_dir("QCOW", lo->lo_dbgfs_dir); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_dir)) { + ret = -ENODEV; + goto out; + } + + qcow_data->dbgfs_file_qcow_header = debugfs_create_file("header", + S_IRUGO, qcow_data->dbgfs_dir, lo_fmt, + &qcow_file_fmt_dbgfs_hdr_fops); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_header)) { + ret = -ENODEV; + goto out_free_dbgfs_dir; + } + + qcow_data->dbgfs_file_qcow_offset = debugfs_create_file("offset", + S_IRUGO | S_IWUSR, qcow_data->dbgfs_dir, lo_fmt, + &qcow_file_fmt_dbgfs_ofs_fops); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_offset)) { + qcow_data->dbgfs_file_qcow_offset = NULL; + ret = -ENODEV; + goto out_free_dbgfs_hdr; + } + + qcow_data->dbgfs_qcow_offset = 0; + mutex_init(&qcow_data->dbgfs_qcow_offset_mutex); + + return ret; + +out_free_dbgfs_hdr: + debugfs_remove(qcow_data->dbgfs_file_qcow_header); + qcow_data->dbgfs_file_qcow_header = NULL; +out_free_dbgfs_dir: + debugfs_remove(qcow_data->dbgfs_dir); + qcow_data->dbgfs_dir = NULL; +out: + return ret; +} + +static void __qcow_file_fmt_dbgfs_exit(struct loop_file_fmt *lo_fmt) +{ + struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; + + if (qcow_data->dbgfs_file_qcow_offset) + debugfs_remove(qcow_data->dbgfs_file_qcow_offset); + + mutex_destroy(&qcow_data->dbgfs_qcow_offset_mutex); + + if (qcow_data->dbgfs_file_qcow_header) + debugfs_remove(qcow_data->dbgfs_file_qcow_header); + + if (qcow_data->dbgfs_dir) + debugfs_remove(qcow_data->dbgfs_dir); +} +#endif + static int qcow_file_fmt_init(struct loop_file_fmt *lo_fmt) { struct loop_file_fmt_qcow_data *qcow_data; @@ -266,8 +414,10 @@ static int qcow_file_fmt_init(struct loop_file_fmt *lo_fmt) if (ret) goto free_qcow_data; -#ifdef CONFIG_DEBUG_DRIVER - __qcow_file_fmt_header_print(&header); + /* save information of the header fields in human readable format in + * a file buffer to access it with debugfs */ +#ifdef CONFIG_DEBUG_FS + __qcow_file_fmt_header_to_buf(lo_fmt, &header); #endif qcow_data->qcow_version = header.version; @@ -493,6 +643,13 @@ static int qcow_file_fmt_init(struct loop_file_fmt *lo_fmt) if (ret < 0) goto free_l2_cache; + /* initialize debugfs entries */ +#ifdef CONFIG_DEBUG_FS + ret = __qcow_file_fmt_dbgfs_init(lo_fmt); + if (ret < 0) + goto free_l2_cache; +#endif + return ret; free_l2_cache: @@ -509,6 +666,10 @@ static void qcow_file_fmt_exit(struct loop_file_fmt *lo_fmt) { struct loop_file_fmt_qcow_data *qcow_data = lo_fmt->private_data; +#ifdef CONFIG_DEBUG_FS + __qcow_file_fmt_dbgfs_exit(lo_fmt); +#endif + __qcow_file_fmt_compression_exit(lo_fmt); if (qcow_data->l1_table) { @@ -752,7 +913,7 @@ static loff_t qcow_file_fmt_sector_size(struct loop_file_fmt *lo_fmt) if (lo->lo_sizelimit > 0 && lo->lo_sizelimit < loopsize) loopsize = lo->lo_sizelimit; - printk(KERN_INFO "loop_file_fmt_qcow: sector_size=%lld", loopsize); + /* * Unfortunately, if we want to do I/O on the device, * the number of 512-byte sectors has to fit into a sector_t. diff --git a/drivers/block/loop/loop_file_fmt_qcow_main.h b/drivers/block/loop/loop_file_fmt_qcow_main.h index c04aa4547799..8f2fa32cb422 100644 --- a/drivers/block/loop/loop_file_fmt_qcow_main.h +++ b/drivers/block/loop/loop_file_fmt_qcow_main.h @@ -16,9 +16,14 @@ #define _LINUX_LOOP_FILE_FMT_QCOW_H #include <linux/list.h> +#include <linux/mutex.h> #include <linux/types.h> #include <linux/zlib.h> +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#endif + #include "loop_file_fmt.h" #ifdef CONFIG_DEBUG_DRIVER @@ -89,6 +94,14 @@ do { \ #define QCOW_DEFAULT_CLUSTER_SIZE 65536 +/* Buffer size for debugfs file buffer to display QCOW header information */ +#define QCOW_HEADER_BUF_LEN 1024 + +/* Buffer size for debugfs file buffer to receive and display offset and + * cluster offset information */ +#define QCOW_OFFSET_BUF_LEN 32 +#define QCOW_CLUSTER_BUF_LEN 128 + struct loop_file_fmt_qcow_header { u32 magic; u32 version; @@ -221,6 +234,18 @@ struct loop_file_fmt_qcow_data { u64 autoclear_features; struct z_stream_s *strm; + + /* debugfs entries */ +#ifdef CONFIG_DEBUG_FS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_file_qcow_header; + char dbgfs_file_qcow_header_buf[QCOW_HEADER_BUF_LEN]; + struct dentry *dbgfs_file_qcow_offset; + char dbgfs_file_qcow_offset_buf[QCOW_OFFSET_BUF_LEN]; + char dbgfs_file_qcow_cluster_buf[QCOW_CLUSTER_BUF_LEN]; + u64 dbgfs_qcow_offset; + struct mutex dbgfs_qcow_offset_mutex; +#endif }; struct loop_file_fmt_qcow_cow_region { diff --git a/drivers/block/loop/loop_main.c b/drivers/block/loop/loop_main.c index 09f001f0690c..1150956974fb 100644 --- a/drivers/block/loop/loop_main.c +++ b/drivers/block/loop/loop_main.c @@ -1667,6 +1667,8 @@ static const struct blk_mq_ops loop_mq_ops = { .complete = lo_complete_rq, }; +static struct dentry *loop_dbgfs_dir; + static int loop_add(struct loop_device **l, int i) { struct loop_device *lo; @@ -1766,6 +1768,21 @@ static int loop_add(struct loop_device **l, int i) sprintf(disk->disk_name, "loop%d", i); add_disk(disk); *l = lo; + + /* initialize debugfs entries */ + /* create for each loop device a debugfs directory under 'loop' if + * the 'block' directory exists, otherwise create the loop directory in + * the root directory */ +#ifdef CONFIG_DEBUG_FS + lo->lo_dbgfs_dir = debugfs_create_dir(disk->disk_name, loop_dbgfs_dir); + + if (IS_ERR_OR_NULL(lo->lo_dbgfs_dir)) { + err = -ENODEV; + lo->lo_dbgfs_dir = NULL; + goto out_free_file_fmt; + } +#endif + return lo->lo_number; out_free_file_fmt: @@ -1785,6 +1802,7 @@ out: static void loop_remove(struct loop_device *lo) { loop_file_fmt_free(lo->lo_fmt); + debugfs_remove(lo->lo_dbgfs_dir); del_gendisk(lo->lo_disk); blk_cleanup_queue(lo->lo_queue); blk_mq_free_tag_set(&lo->tag_set); @@ -1972,6 +1990,14 @@ static int __init loop_init(void) goto misc_out; } +#ifdef CONFIG_DEBUG_FS + loop_dbgfs_dir = debugfs_create_dir("loop", NULL); + if (IS_ERR_OR_NULL(loop_dbgfs_dir)) { + err = -ENODEV; + goto misc_out; + } +#endif + blk_register_region(MKDEV(LOOP_MAJOR, 0), range, THIS_MODULE, loop_probe, NULL, NULL); @@ -2010,6 +2036,10 @@ static void __exit loop_exit(void) blk_unregister_region(MKDEV(LOOP_MAJOR, 0), range); unregister_blkdev(LOOP_MAJOR, "loop"); +#ifdef CONFIG_DEBUG_FS + debugfs_remove(loop_dbgfs_dir); +#endif + misc_deregister(&loop_misc); } diff --git a/drivers/block/loop/loop_main.h b/drivers/block/loop/loop_main.h index 088830fb379e..33f6578d54c7 100644 --- a/drivers/block/loop/loop_main.h +++ b/drivers/block/loop/loop_main.h @@ -17,6 +17,10 @@ #include <linux/kthread.h> #include <uapi/linux/loop.h> +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#endif + #include "loop_file_fmt.h" /* Possible states of device */ @@ -66,6 +70,10 @@ struct loop_device { struct request_queue *lo_queue; struct blk_mq_tag_set tag_set; struct gendisk *lo_disk; + +#ifdef CONFIG_DEBUG_FS + struct dentry *lo_dbgfs_dir; +#endif }; struct loop_cmd { |