summaryrefslogblamecommitdiffstats
path: root/src/kernel/xloop_file_fmt_qcow_cache.c
blob: d9701b6fe0d285bdb8673f014c08ed3f9007a7bb (plain) (tree)
1
2
3
4
5
                                   
  
                              
  
                                                       









                                                                    

                                           





                          
                         
 

                                      
 
                                                                                                              
 
                                                                    

 
                                                                                                             
 
                                                                    



                                                       
 



                                                                                   


                   

                                                                                                   
 
                                                                           










                                                                

                                                                                                                  

                          
                                                                           
      
                                            






                                                           
               
                            


                                   
                                                                                          











                                                             
                                                                      
 

                                                                           

              
                                     
                                               





                              

                                                                                                                       



                                                            

                                                                                                                       


         

                                                                                                                  
 
                                                                   





                                      
                                  




                                                 


                                                                                                 



                                                  


                                                                   
            


                                                                                 
                                   
 



                                                                      
                                   
                              
 



                                    
                                                                                                                   




                                                           
                                                                     
                    
                           



                                     

                                                                                                          










                                        
                                                                  



                 
                                                                                           
 

                                                                           
 
                                                                                   

 
                                                                                
 


                                                                           



                            
                                   
                                                             


                                       
// SPDX-License-Identifier: GPL-2.0
/*
 * xloop_file_fmt_qcow_cache.c
 *
 * QCOW file format driver for the xloop device module.
 *
 * Ported QCOW2 implementation of the QEMU project (GPL-2.0):
 * L2/refcount table cache for the QCOW2 format.
 *
 * The copyright (C) 2010 of the original code is owned by
 * Kevin Wolf <kwolf@redhat.com>
 *
 * Copyright (C) 2019 Manuel Bentele <development@manuel-bentele.de>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/types.h>
#include <linux/limits.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
#include <linux/math64.h>

#include "xloop_file_fmt_qcow_main.h"
#include "xloop_file_fmt_qcow_cache.h"

static inline void *__xloop_file_fmt_qcow_cache_get_table_addr(struct xloop_file_fmt_qcow_cache *c, int table)
{
	return (u8 *)c->table_array + (size_t)table * c->table_size;
}

static inline int __xloop_file_fmt_qcow_cache_get_table_idx(struct xloop_file_fmt_qcow_cache *c, void *table)
{
	ptrdiff_t table_offset = (u8 *)table - (u8 *)c->table_array;
	int idx = div_s64(table_offset, c->table_size);

#ifdef ASSERT
	s32 rem_table_offset_mod_table_size;

	div_s64_rem(table_offset, c->table_size, &rem_table_offset_mod_table_size);
	ASSERT(idx >= 0 && idx < c->size && rem_table_offset_mod_table_size == 0);
#endif

	return idx;
}

static inline const char *__xloop_file_fmt_qcow_cache_get_name(struct xloop_file_fmt *xlo_fmt,
							       struct xloop_file_fmt_qcow_cache *c)
{
	struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;

	if (c == qcow_data->refcount_block_cache) {
		return "refcount block";
	} else if (c == qcow_data->l2_table_cache) {
		return "L2 table";
	} else {
		/* do not abort, because this is not critical */
		return "unknown";
	}
}

struct xloop_file_fmt_qcow_cache *xloop_file_fmt_qcow_cache_create(struct xloop_file_fmt *xlo_fmt, int num_tables,
								   unsigned int table_size)
{
#ifdef CONFIG_DEBUG_DRIVER
	struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
#endif
	struct xloop_file_fmt_qcow_cache *c;

	ASSERT(num_tables > 0);
	ASSERT(is_power_of_2(table_size));
	ASSERT(table_size >= (1 << QCOW_MIN_CLUSTER_BITS));
	ASSERT(table_size <= qcow_data->cluster_size);

	c = kzalloc(sizeof(*c), GFP_KERNEL);
	if (!c)
		return NULL;

	c->size = num_tables;
	c->table_size = table_size;
	c->entries = vzalloc(sizeof(struct xloop_file_fmt_qcow_cache_table) * num_tables);
	c->table_array = vzalloc(num_tables * c->table_size);

	if (!c->entries || !c->table_array) {
		vfree(c->table_array);
		vfree(c->entries);
		kfree(c);
		c = NULL;
	}

	return c;
}

void xloop_file_fmt_qcow_cache_destroy(struct xloop_file_fmt *xlo_fmt)
{
	struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
	struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache;
	int i;

	for (i = 0; i < c->size; i++)
		ASSERT(c->entries[i].ref == 0);

	vfree(c->table_array);
	vfree(c->entries);
	kfree(c);
}

static int __xloop_file_fmt_qcow_cache_entry_flush(struct xloop_file_fmt *xlo_fmt, struct xloop_file_fmt_qcow_cache *c,
						   int i)
{
	if (!c->entries[i].dirty || !c->entries[i].offset) {
		return 0;
	} else {
		dev_err_ratelimited(xloop_file_fmt_to_dev(xlo_fmt), "flush dirty cache tables is not supported yet\n");
		return -EINVAL;
	}
}

static int __xloop_file_fmt_qcow_cache_do_get(struct xloop_file_fmt *xlo_fmt, struct xloop_file_fmt_qcow_cache *c,
					      u64 offset, void **table, bool read_from_disk)
{
	struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt);
	int i;
	int ret;
	int lookup_index;
	u64 min_lru_counter = U64_MAX;
	int min_lru_index = -1;
	u64 read_offset;
	u64 offset_div_table_size;
	size_t len;

	ASSERT(offset != 0);

	if (!IS_ALIGNED(offset, c->table_size)) {
		dev_err_ratelimited(xloop_file_fmt_to_dev(xlo_fmt),
				    "cannot get entry from %s cache: offset %llx is unaligned\n",
				    __xloop_file_fmt_qcow_cache_get_name(xlo_fmt, c), offset);
		return -EIO;
	}

	/* Check if the table is already cached */
	offset_div_table_size = div_u64(offset, c->table_size) * 4;
	div_u64_rem(offset_div_table_size, c->size, &lookup_index);
	i = lookup_index;
	do {
		const struct xloop_file_fmt_qcow_cache_table *t = &c->entries[i];

		if (t->offset == offset)
			goto found;

		if (t->ref == 0 && t->lru_counter < min_lru_counter) {
			min_lru_counter = t->lru_counter;
			min_lru_index = i;
		}
		if (++i == c->size)
			i = 0;

	} while (i != lookup_index);

	if (min_lru_index == -1) {
		BUG();
		panic("Oops: This can't happen in current synchronous code while using AIO with the QCOW cache\n");
	}

	/* Cache miss: write a table back and replace it */
	i = min_lru_index;

	ret = __xloop_file_fmt_qcow_cache_entry_flush(xlo_fmt, c, i);
	if (ret < 0)
		return ret;

	c->entries[i].offset = 0;
	if (read_from_disk) {
		read_offset = offset;
		len = kernel_read(xlo->xlo_backing_file, __xloop_file_fmt_qcow_cache_get_table_addr(c, i),
				  c->table_size, &read_offset);
		if (len < 0) {
			len = ret;
			return ret;
		}
	}

	c->entries[i].offset = offset;

	/* And return the right table */
found:
	c->entries[i].ref++;
	*table = __xloop_file_fmt_qcow_cache_get_table_addr(c, i);

	return 0;
}

int xloop_file_fmt_qcow_cache_get(struct xloop_file_fmt *xlo_fmt, u64 offset, void **table)
{
	struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
	struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache;

	return __xloop_file_fmt_qcow_cache_do_get(xlo_fmt, c, offset, table, true);
}

void xloop_file_fmt_qcow_cache_put(struct xloop_file_fmt *xlo_fmt, void **table)
{
	struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
	struct xloop_file_fmt_qcow_cache *c = qcow_data->l2_table_cache;
	int i = __xloop_file_fmt_qcow_cache_get_table_idx(c, *table);

	c->entries[i].ref--;
	*table = NULL;

	if (c->entries[i].ref == 0)
		c->entries[i].lru_counter = ++c->lru_counter;

	ASSERT(c->entries[i].ref >= 0);
}