aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/staging/ramzswap/ramzswap_drv.c
blob: 7b5cc61b3d697adc6854d1619bf8bfe3f133a708 (plain) (tree)
1
2
3
4


                                   
                                              




















                                                                     
                       
                      



                          






                                



































                                                                




























                                                                              
                                                          

                                                       
                                    








                                                                  

                                                                 







                                                            





                                                                    









                                                           



                                  








                                                                  



                                                                

                                                             
                                                             







                                                                    
                                                       








                                                                    
                                                        


                                      
                                               




                                      
                                               

                       




                                               
                                

 

                                                             
 

                                       







                                                              
                                



                                                               

              
                  
                             
 
                                                   
 
                                                         





                                               
 
                                     
 



                                                          
 






                                                                          
 




                                                                            
 

                                                       
 

                                                                      
 



                                                                    
 

                                                  
 










                                                                         











                                                                


                             
 
                                                    
 

                                                         






                                                     
 

                                           
 






                                                                           
 
                                       
 







                                                             
 

                                                                       
 
                                                  
 
                                                
                                                 
                                                                    
                                                                       


                                 






















                                                                               
 








                                                                         

         
                                                  
 

                                                                      

     





                                                                      

      
                                        
 


                                                                          
 




                                                                
 


                                         





                                              



                          


                                                      
                                                                         



                                                                      
                                                    



                         
                                  















                                                                              
                                          
                                                            


















                                               
                     
 


                                               







                                                                   
                                                                       














                                                                          





                                       



                                                   





                                                           





                                                         
                                                                 

























                                                                        

                                                               

                                                                     







                                                       























                                                                  
                           

















                                                                  






























                                                                       




                                                           











                                                       






                                                                              

 
                                                               
                                

                                                           

  
                                                             
 

                    
                               
                                          




                                                                     

                              










                                                                             

                              








                                                                    
                                                                   
                                   
 



                                                         

                                                                   

                                                      
 


                            


                   














                                                
                        



                                                                 

                              




                                                           

                             









                                                                             



                                
 


                                                              
                                          

         
                 
 
             


                                                   
                                                      
    






























                                                            
/*
 * Compressed RAM based swap device
 *
 * Copyright (C) 2008, 2009, 2010  Nitin Gupta
 *
 * This code is released using a dual license strategy: BSD/GPL
 * You can choose the licence that better fits your requirements.
 *
 * Released under the terms of 3-clause BSD License
 * Released under the terms of GNU General Public License Version 2.0
 *
 * Project home: http://compcache.googlecode.com
 */

#define KMSG_COMPONENT "ramzswap"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/bitops.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/device.h>
#include <linux/genhd.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <linux/lzo.h>
#include <linux/string.h>
#include <linux/swap.h>
#include <linux/swapops.h>
#include <linux/vmalloc.h>

#include "ramzswap_drv.h"

/* Globals */
static int ramzswap_major;
static struct ramzswap *devices;

/* Module params (documentation at end) */
static unsigned int num_devices;

static int rzs_test_flag(struct ramzswap *rzs, u32 index,
			enum rzs_pageflags flag)
{
	return rzs->table[index].flags & BIT(flag);
}

static void rzs_set_flag(struct ramzswap *rzs, u32 index,
			enum rzs_pageflags flag)
{
	rzs->table[index].flags |= BIT(flag);
}

static void rzs_clear_flag(struct ramzswap *rzs, u32 index,
			enum rzs_pageflags flag)
{
	rzs->table[index].flags &= ~BIT(flag);
}

static int page_zero_filled(void *ptr)
{
	unsigned int pos;
	unsigned long *page;

	page = (unsigned long *)ptr;

	for (pos = 0; pos != PAGE_SIZE / sizeof(*page); pos++) {
		if (page[pos])
			return 0;
	}

	return 1;
}

static void ramzswap_set_disksize(struct ramzswap *rzs, size_t totalram_bytes)
{
	if (!rzs->disksize) {
		pr_info(
		"disk size not provided. You can use disksize_kb module "
		"param to specify size.\nUsing default: (%u%% of RAM).\n",
		default_disksize_perc_ram
		);
		rzs->disksize = default_disksize_perc_ram *
					(totalram_bytes / 100);
	}

	if (rzs->disksize > 2 * (totalram_bytes)) {
		pr_info(
		"There is little point creating a ramzswap of greater than "
		"twice the size of memory since we expect a 2:1 compression "
		"ratio. Note that ramzswap uses about 0.1%% of the size of "
		"the swap device when not in use so a huge ramzswap is "
		"wasteful.\n"
		"\tMemory Size: %zu kB\n"
		"\tSize you selected: %zu kB\n"
		"Continuing anyway ...\n",
		totalram_bytes >> 10, rzs->disksize
		);
	}

	rzs->disksize &= PAGE_MASK;
}

static void ramzswap_ioctl_get_stats(struct ramzswap *rzs,
			struct ramzswap_ioctl_stats *s)
{
	s->disksize = rzs->disksize;

#if defined(CONFIG_RAMZSWAP_STATS)
	{
	struct ramzswap_stats *rs = &rzs->stats;
	size_t succ_writes, mem_used;
	unsigned int good_compress_perc = 0, no_compress_perc = 0;

	mem_used = xv_get_total_size_bytes(rzs->mem_pool)
			+ (rs->pages_expand << PAGE_SHIFT);
	succ_writes = rzs_stat64_read(rzs, &rs->num_writes) -
			rzs_stat64_read(rzs, &rs->failed_writes);

	if (succ_writes && rs->pages_stored) {
		good_compress_perc = rs->good_compress * 100
					/ rs->pages_stored;
		no_compress_perc = rs->pages_expand * 100
					/ rs->pages_stored;
	}

	s->num_reads = rzs_stat64_read(rzs, &rs->num_reads);
	s->num_writes = rzs_stat64_read(rzs, &rs->num_writes);
	s->failed_reads = rzs_stat64_read(rzs, &rs->failed_reads);
	s->failed_writes = rzs_stat64_read(rzs, &rs->failed_writes);
	s->invalid_io = rzs_stat64_read(rzs, &rs->invalid_io);
	s->notify_free = rzs_stat64_read(rzs, &rs->notify_free);
	s->pages_zero = rs->pages_zero;

	s->good_compress_pct = good_compress_perc;
	s->pages_expand_pct = no_compress_perc;

	s->pages_stored = rs->pages_stored;
	s->pages_used = mem_used >> PAGE_SHIFT;
	s->orig_data_size = rs->pages_stored << PAGE_SHIFT;
	s->compr_data_size = rs->compr_size;
	s->mem_used_total = mem_used;
	}
#endif /* CONFIG_RAMZSWAP_STATS */
}

static void ramzswap_free_page(struct ramzswap *rzs, size_t index)
{
	u32 clen;
	void *obj;

	struct page *page = rzs->table[index].page;
	u32 offset = rzs->table[index].offset;

	if (unlikely(!page)) {
		/*
		 * No memory is allocated for zero filled pages.
		 * Simply clear zero page flag.
		 */
		if (rzs_test_flag(rzs, index, RZS_ZERO)) {
			rzs_clear_flag(rzs, index, RZS_ZERO);
			rzs_stat_dec(&rzs->stats.pages_zero);
		}
		return;
	}

	if (unlikely(rzs_test_flag(rzs, index, RZS_UNCOMPRESSED))) {
		clen = PAGE_SIZE;
		__free_page(page);
		rzs_clear_flag(rzs, index, RZS_UNCOMPRESSED);
		rzs_stat_dec(&rzs->stats.pages_expand);
		goto out;
	}

	obj = kmap_atomic(page, KM_USER0) + offset;
	clen = xv_get_object_size(obj) - sizeof(struct zobj_header);
	kunmap_atomic(obj, KM_USER0);

	xv_free(rzs->mem_pool, page, offset);
	if (clen <= PAGE_SIZE / 2)
		rzs_stat_dec(&rzs->stats.good_compress);

out:
	rzs->stats.compr_size -= clen;
	rzs_stat_dec(&rzs->stats.pages_stored);

	rzs->table[index].page = NULL;
	rzs->table[index].offset = 0;
}

static void handle_zero_page(struct page *page)
{
	void *user_mem;

	user_mem = kmap_atomic(page, KM_USER0);
	memset(user_mem, 0, PAGE_SIZE);
	kunmap_atomic(user_mem, KM_USER0);

	flush_dcache_page(page);
}

static void handle_uncompressed_page(struct ramzswap *rzs,
				struct page *page, u32 index)
{
	unsigned char *user_mem, *cmem;

	user_mem = kmap_atomic(page, KM_USER0);
	cmem = kmap_atomic(rzs->table[index].page, KM_USER1) +
			rzs->table[index].offset;

	memcpy(user_mem, cmem, PAGE_SIZE);
	kunmap_atomic(user_mem, KM_USER0);
	kunmap_atomic(cmem, KM_USER1);

	flush_dcache_page(page);
}

static int ramzswap_read(struct ramzswap *rzs, struct bio *bio)
{

	int i;
	u32 index;
	struct bio_vec *bvec;

	rzs_stat64_inc(rzs, &rzs->stats.num_reads);

	index = bio->bi_sector >> SECTORS_PER_PAGE_SHIFT;
	bio_for_each_segment(bvec, bio, i) {
		int ret;
		size_t clen;
		struct page *page;
		struct zobj_header *zheader;
		unsigned char *user_mem, *cmem;

		page = bvec->bv_page;

		if (rzs_test_flag(rzs, index, RZS_ZERO)) {
			handle_zero_page(page);
			continue;
		}

		/* Requested page is not present in compressed area */
		if (unlikely(!rzs->table[index].page)) {
			pr_debug("Read before write: sector=%lu, size=%u",
				(ulong)(bio->bi_sector), bio->bi_size);
			/* Do nothing */
			continue;
		}

		/* Page is stored uncompressed since it's incompressible */
		if (unlikely(rzs_test_flag(rzs, index, RZS_UNCOMPRESSED))) {
			handle_uncompressed_page(rzs, page, index);
			continue;
		}

		user_mem = kmap_atomic(page, KM_USER0);
		clen = PAGE_SIZE;

		cmem = kmap_atomic(rzs->table[index].page, KM_USER1) +
				rzs->table[index].offset;

		ret = lzo1x_decompress_safe(
			cmem + sizeof(*zheader),
			xv_get_object_size(cmem) - sizeof(*zheader),
			user_mem, &clen);

		kunmap_atomic(user_mem, KM_USER0);
		kunmap_atomic(cmem, KM_USER1);

		/* Should NEVER happen. Return bio error if it does. */
		if (unlikely(ret != LZO_E_OK)) {
			pr_err("Decompression failed! err=%d, page=%u\n",
				ret, index);
			rzs_stat64_inc(rzs, &rzs->stats.failed_reads);
			goto out;
		}

		flush_dcache_page(page);
		index++;
	}

	set_bit(BIO_UPTODATE, &bio->bi_flags);
	bio_endio(bio, 0);
	return 0;

out:
	bio_io_error(bio);
	return 0;
}

static int ramzswap_write(struct ramzswap *rzs, struct bio *bio)
{
	int i;
	u32 index;
	struct bio_vec *bvec;

	rzs_stat64_inc(rzs, &rzs->stats.num_writes);

	index = bio->bi_sector >> SECTORS_PER_PAGE_SHIFT;

	bio_for_each_segment(bvec, bio, i) {
		int ret;
		u32 offset;
		size_t clen;
		struct zobj_header *zheader;
		struct page *page, *page_store;
		unsigned char *user_mem, *cmem, *src;

		page = bvec->bv_page;
		src = rzs->compress_buffer;

		/*
		 * System overwrites unused sectors. Free memory associated
		 * with this sector now.
		 */
		if (rzs->table[index].page ||
				rzs_test_flag(rzs, index, RZS_ZERO))
			ramzswap_free_page(rzs, index);

		mutex_lock(&rzs->lock);

		user_mem = kmap_atomic(page, KM_USER0);
		if (page_zero_filled(user_mem)) {
			kunmap_atomic(user_mem, KM_USER0);
			mutex_unlock(&rzs->lock);
			rzs_stat_inc(&rzs->stats.pages_zero);
			rzs_set_flag(rzs, index, RZS_ZERO);
			continue;
		}

		ret = lzo1x_1_compress(user_mem, PAGE_SIZE, src, &clen,
					rzs->compress_workmem);

		kunmap_atomic(user_mem, KM_USER0);

		if (unlikely(ret != LZO_E_OK)) {
			mutex_unlock(&rzs->lock);
			pr_err("Compression failed! err=%d\n", ret);
			rzs_stat64_inc(rzs, &rzs->stats.failed_writes);
			goto out;
		}

		/*
		 * Page is incompressible. Store it as-is (uncompressed)
		 * since we do not want to return too many swap write
		 * errors which has side effect of hanging the system.
		 */
		if (unlikely(clen > max_zpage_size)) {
			clen = PAGE_SIZE;
			page_store = alloc_page(GFP_NOIO | __GFP_HIGHMEM);
			if (unlikely(!page_store)) {
				mutex_unlock(&rzs->lock);
				pr_info("Error allocating memory for "
					"incompressible page: %u\n", index);
				rzs_stat64_inc(rzs, &rzs->stats.failed_writes);
				goto out;
			}

			offset = 0;
			rzs_set_flag(rzs, index, RZS_UNCOMPRESSED);
			rzs_stat_inc(&rzs->stats.pages_expand);
			rzs->table[index].page = page_store;
			src = kmap_atomic(page, KM_USER0);
			goto memstore;
		}

		if (xv_malloc(rzs->mem_pool, clen + sizeof(*zheader),
				&rzs->table[index].page, &offset,
				GFP_NOIO | __GFP_HIGHMEM)) {
			mutex_unlock(&rzs->lock);
			pr_info("Error allocating memory for compressed "
				"page: %u, size=%zu\n", index, clen);
			rzs_stat64_inc(rzs, &rzs->stats.failed_writes);
			goto out;
		}

memstore:
		rzs->table[index].offset = offset;

		cmem = kmap_atomic(rzs->table[index].page, KM_USER1) +
				rzs->table[index].offset;

#if 0
		/* Back-reference needed for memory defragmentation */
		if (!rzs_test_flag(rzs, index, RZS_UNCOMPRESSED)) {
			zheader = (struct zobj_header *)cmem;
			zheader->table_idx = index;
			cmem += sizeof(*zheader);
		}
#endif

		memcpy(cmem, src, clen);

		kunmap_atomic(cmem, KM_USER1);
		if (unlikely(rzs_test_flag(rzs, index, RZS_UNCOMPRESSED)))
			kunmap_atomic(src, KM_USER0);

		/* Update stats */
		rzs->stats.compr_size += clen;
		rzs_stat_inc(&rzs->stats.pages_stored);
		if (clen <= PAGE_SIZE / 2)
			rzs_stat_inc(&rzs->stats.good_compress);

		mutex_unlock(&rzs->lock);
		index++;
	}

	set_bit(BIO_UPTODATE, &bio->bi_flags);
	bio_endio(bio, 0);
	return 0;

out:
	bio_io_error(bio);
	return 0;
}

/*
 * Check if request is within bounds and page aligned.
 */
static inline int valid_io_request(struct ramzswap *rzs, struct bio *bio)
{
	if (unlikely(
		(bio->bi_sector >= (rzs->disksize >> SECTOR_SHIFT)) ||
		(bio->bi_sector & (SECTORS_PER_PAGE - 1)) ||
		(bio->bi_size & (PAGE_SIZE - 1)))) {

		return 0;
	}

	/* I/O request is valid */
	return 1;
}

/*
 * Handler function for all ramzswap I/O requests.
 */
static int ramzswap_make_request(struct request_queue *queue, struct bio *bio)
{
	int ret = 0;
	struct ramzswap *rzs = queue->queuedata;

	if (unlikely(!rzs->init_done)) {
		bio_io_error(bio);
		return 0;
	}

	if (!valid_io_request(rzs, bio)) {
		rzs_stat64_inc(rzs, &rzs->stats.invalid_io);
		bio_io_error(bio);
		return 0;
	}

	switch (bio_data_dir(bio)) {
	case READ:
		ret = ramzswap_read(rzs, bio);
		break;

	case WRITE:
		ret = ramzswap_write(rzs, bio);
		break;
	}

	return ret;
}

static void reset_device(struct ramzswap *rzs)
{
	size_t index;

	/* Do not accept any new I/O request */
	rzs->init_done = 0;

	/* Free various per-device buffers */
	kfree(rzs->compress_workmem);
	free_pages((unsigned long)rzs->compress_buffer, 1);

	rzs->compress_workmem = NULL;
	rzs->compress_buffer = NULL;

	/* Free all pages that are still in this ramzswap device */
	for (index = 0; index < rzs->disksize >> PAGE_SHIFT; index++) {
		struct page *page;
		u16 offset;

		page = rzs->table[index].page;
		offset = rzs->table[index].offset;

		if (!page)
			continue;

		if (unlikely(rzs_test_flag(rzs, index, RZS_UNCOMPRESSED)))
			__free_page(page);
		else
			xv_free(rzs->mem_pool, page, offset);
	}

	vfree(rzs->table);
	rzs->table = NULL;

	xv_destroy_pool(rzs->mem_pool);
	rzs->mem_pool = NULL;

	/* Reset stats */
	memset(&rzs->stats, 0, sizeof(rzs->stats));

	rzs->disksize = 0;
}

static int ramzswap_ioctl_init_device(struct ramzswap *rzs)
{
	int ret;
	size_t num_pages;

	if (rzs->init_done) {
		pr_info("Device already initialized!\n");
		return -EBUSY;
	}

	ramzswap_set_disksize(rzs, totalram_pages << PAGE_SHIFT);

	rzs->compress_workmem = kzalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL);
	if (!rzs->compress_workmem) {
		pr_err("Error allocating compressor working memory!\n");
		ret = -ENOMEM;
		goto fail;
	}

	rzs->compress_buffer = (void *)__get_free_pages(__GFP_ZERO, 1);
	if (!rzs->compress_buffer) {
		pr_err("Error allocating compressor buffer space\n");
		ret = -ENOMEM;
		goto fail;
	}

	num_pages = rzs->disksize >> PAGE_SHIFT;
	rzs->table = vmalloc(num_pages * sizeof(*rzs->table));
	if (!rzs->table) {
		pr_err("Error allocating ramzswap address table\n");
		/* To prevent accessing table entries during cleanup */
		rzs->disksize = 0;
		ret = -ENOMEM;
		goto fail;
	}
	memset(rzs->table, 0, num_pages * sizeof(*rzs->table));

	set_capacity(rzs->disk, rzs->disksize >> SECTOR_SHIFT);

	/* ramzswap devices sort of resembles non-rotational disks */
	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, rzs->disk->queue);

	rzs->mem_pool = xv_create_pool();
	if (!rzs->mem_pool) {
		pr_err("Error creating memory pool\n");
		ret = -ENOMEM;
		goto fail;
	}

	rzs->init_done = 1;

	pr_debug("Initialization done!\n");
	return 0;

fail:
	reset_device(rzs);

	pr_err("Initialization failed: err=%d\n", ret);
	return ret;
}

static int ramzswap_ioctl_reset_device(struct ramzswap *rzs)
{
	if (rzs->init_done)
		reset_device(rzs);

	return 0;
}

static int ramzswap_ioctl(struct block_device *bdev, fmode_t mode,
			unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	size_t disksize_kb;

	struct ramzswap *rzs = bdev->bd_disk->private_data;

	switch (cmd) {
	case RZSIO_SET_DISKSIZE_KB:
		if (rzs->init_done) {
			ret = -EBUSY;
			goto out;
		}
		if (copy_from_user(&disksize_kb, (void *)arg,
						_IOC_SIZE(cmd))) {
			ret = -EFAULT;
			goto out;
		}
		rzs->disksize = disksize_kb << 10;
		pr_info("Disk size set to %zu kB\n", disksize_kb);
		break;

	case RZSIO_GET_STATS:
	{
		struct ramzswap_ioctl_stats *stats;
		if (!rzs->init_done) {
			ret = -ENOTTY;
			goto out;
		}
		stats = kzalloc(sizeof(*stats), GFP_KERNEL);
		if (!stats) {
			ret = -ENOMEM;
			goto out;
		}
		ramzswap_ioctl_get_stats(rzs, stats);
		if (copy_to_user((void *)arg, stats, sizeof(*stats))) {
			kfree(stats);
			ret = -EFAULT;
			goto out;
		}
		kfree(stats);
		break;
	}
	case RZSIO_INIT:
		ret = ramzswap_ioctl_init_device(rzs);
		break;

	case RZSIO_RESET:
		/* Do not reset an active device! */
		if (bdev->bd_holders) {
			ret = -EBUSY;
			goto out;
		}

		/* Make sure all pending I/O is finished */
		if (bdev)
			fsync_bdev(bdev);

		ret = ramzswap_ioctl_reset_device(rzs);
		break;

	default:
		pr_info("Invalid ioctl %u\n", cmd);
		ret = -ENOTTY;
	}

out:
	return ret;
}

void ramzswap_slot_free_notify(struct block_device *bdev, unsigned long index)
{
	struct ramzswap *rzs;

	rzs = bdev->bd_disk->private_data;
	ramzswap_free_page(rzs, index);
	rzs_stat64_inc(rzs, &rzs->stats.notify_free);
}

static const struct block_device_operations ramzswap_devops = {
	.ioctl = ramzswap_ioctl,
	.swap_slot_free_notify = ramzswap_slot_free_notify,
	.owner = THIS_MODULE
};

static int create_device(struct ramzswap *rzs, int device_id)
{
	int ret = 0;

	mutex_init(&rzs->lock);
	spin_lock_init(&rzs->stat64_lock);

	rzs->queue = blk_alloc_queue(GFP_KERNEL);
	if (!rzs->queue) {
		pr_err("Error allocating disk queue for device %d\n",
			device_id);
		ret = -ENOMEM;
		goto out;
	}

	blk_queue_make_request(rzs->queue, ramzswap_make_request);
	rzs->queue->queuedata = rzs;

	 /* gendisk structure */
	rzs->disk = alloc_disk(1);
	if (!rzs->disk) {
		blk_cleanup_queue(rzs->queue);
		pr_warning("Error allocating disk structure for device %d\n",
			device_id);
		ret = -ENOMEM;
		goto out;
	}

	rzs->disk->major = ramzswap_major;
	rzs->disk->first_minor = device_id;
	rzs->disk->fops = &ramzswap_devops;
	rzs->disk->queue = rzs->queue;
	rzs->disk->private_data = rzs;
	snprintf(rzs->disk->disk_name, 16, "ramzswap%d", device_id);

	/* Actual capacity set using RZSIO_SET_DISKSIZE_KB ioctl */
	set_capacity(rzs->disk, 0);

	/*
	 * To ensure that we always get PAGE_SIZE aligned
	 * and n*PAGE_SIZED sized I/O requests.
	 */
	blk_queue_physical_block_size(rzs->disk->queue, PAGE_SIZE);
	blk_queue_logical_block_size(rzs->disk->queue, PAGE_SIZE);
	blk_queue_io_min(rzs->disk->queue, PAGE_SIZE);
	blk_queue_io_opt(rzs->disk->queue, PAGE_SIZE);

	add_disk(rzs->disk);

	rzs->init_done = 0;

out:
	return ret;
}

static void destroy_device(struct ramzswap *rzs)
{
	if (rzs->disk) {
		del_gendisk(rzs->disk);
		put_disk(rzs->disk);
	}

	if (rzs->queue)
		blk_cleanup_queue(rzs->queue);
}

static int __init ramzswap_init(void)
{
	int ret, dev_id;

	if (num_devices > max_num_devices) {
		pr_warning("Invalid value for num_devices: %u\n",
				num_devices);
		ret = -EINVAL;
		goto out;
	}

	ramzswap_major = register_blkdev(0, "ramzswap");
	if (ramzswap_major <= 0) {
		pr_warning("Unable to get major number\n");
		ret = -EBUSY;
		goto out;
	}

	if (!num_devices) {
		pr_info("num_devices not specified. Using default: 1\n");
		num_devices = 1;
	}

	/* Allocate the device array and initialize each one */
	pr_info("Creating %u devices ...\n", num_devices);
	devices = kzalloc(num_devices * sizeof(struct ramzswap), GFP_KERNEL);
	if (!devices) {
		ret = -ENOMEM;
		goto unregister;
	}

	for (dev_id = 0; dev_id < num_devices; dev_id++) {
		ret = create_device(&devices[dev_id], dev_id);
		if (ret)
			goto free_devices;
	}

	return 0;

free_devices:
	while (dev_id)
		destroy_device(&devices[--dev_id]);
unregister:
	unregister_blkdev(ramzswap_major, "ramzswap");
out:
	return ret;
}

static void __exit ramzswap_exit(void)
{
	int i;
	struct ramzswap *rzs;

	for (i = 0; i < num_devices; i++) {
		rzs = &devices[i];

		destroy_device(rzs);
		if (rzs->init_done)
			reset_device(rzs);
	}

	unregister_blkdev(ramzswap_major, "ramzswap");

	kfree(devices);
	pr_debug("Cleanup done!\n");
}

module_param(num_devices, uint, 0);
MODULE_PARM_DESC(num_devices, "Number of ramzswap devices");

module_init(ramzswap_init);
module_exit(ramzswap_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Nitin Gupta <ngupta@vflare.org>");
MODULE_DESCRIPTION("Compressed RAM Based Swap Device");