summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/block/loop/loop_file_fmt_qcow_main.c295
-rw-r--r--drivers/block/loop/loop_file_fmt_qcow_main.h25
-rw-r--r--drivers/block/loop/loop_main.c30
-rw-r--r--drivers/block/loop/loop_main.h8
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 {