summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManuel Bentele2019-08-07 16:23:19 +0200
committerManuel Bentele2019-08-21 22:03:37 +0200
commit7d72f198cd47536e6c6cdd07b3f8c6a9cd8a624a (patch)
treedf56a9330373da52523bba4fc7e1928bd9e54d63
parentblock: loop: file_fmt_qcow: set up L2 cache size correctly (diff)
downloadkernel-qcow2-linux-7d72f198cd47536e6c6cdd07b3f8c6a9cd8a624a.tar.gz
kernel-qcow2-linux-7d72f198cd47536e6c6cdd07b3f8c6a9cd8a624a.tar.xz
kernel-qcow2-linux-7d72f198cd47536e6c6cdd07b3f8c6a9cd8a624a.zip
block: loop: add debugfs support for loop devices
The loop device module is extended by providing debugfs support if debugfs is enabled in kconfig. The provided debugfs root directory is called loop and contains for each loop device a subfolder named after its disk name of the block device. These folders can be used by the different loop file format drivers to export debugging information. This patch contains debugfs entries for the QCOW2 file format driver, too. The read-only 'header' entry provides human readable information of the QCOW header. The 'offset' entry provides interaction with the address calulation engine of the QCOW implementation. One linear address number can be written to the 'offset' entry. Later on, after writing to the entry, the calulated non-linear cluster offset can be read. Signed-off-by: Manuel Bentele <development@manuel-bentele.de>
-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 {