source "drivers/staging/iio/Kconfig"
-source "drivers/staging/ramzswap/Kconfig"
+source "drivers/staging/zram/Kconfig"
source "drivers/staging/wlags49_h2/Kconfig"
obj-$(CONFIG_MRST_RAR_HANDLER) += memrar/
obj-$(CONFIG_DX_SEP) += sep/
obj-$(CONFIG_IIO) += iio/
-obj-$(CONFIG_RAMZSWAP) += ramzswap/
+obj-$(CONFIG_ZRAM) += zram/
obj-$(CONFIG_WLAGS49_H2) += wlags49_h2/
obj-$(CONFIG_WLAGS49_H25) += wlags49_h25/
obj-$(CONFIG_BATMAN_ADV) += batman-adv/
+++ /dev/null
-config RAMZSWAP
- tristate "Compressed in-memory swap device (ramzswap)"
- depends on SWAP
- select LZO_COMPRESS
- select LZO_DECOMPRESS
- default n
- help
- Creates virtual block devices which can (only) be used as swap
- disks. Pages swapped to these disks are compressed and stored in
- memory itself.
-
- See ramzswap.txt for more information.
- Project home: http://compcache.googlecode.com/
-
-config RAMZSWAP_STATS
- bool "Enable ramzswap stats"
- depends on RAMZSWAP
- default y
- help
- Enable statistics collection for ramzswap. This adds only a minimal
- overhead. In unsure, say Y.
+++ /dev/null
-ramzswap-objs := ramzswap_drv.o xvmalloc.o
-
-obj-$(CONFIG_RAMZSWAP) += ramzswap.o
+++ /dev/null
-ramzswap: Compressed RAM based swap device
--------------------------------------------
-
-Project home: http://compcache.googlecode.com/
-
-* Introduction
-
-The ramzswap module creates RAM based block devices which can (only) be used as
-swap disks. Pages swapped to these devices are compressed and stored in memory
-itself. See project home for use cases, performance numbers and a lot more.
-
-Individual ramzswap devices are configured and initialized using rzscontrol
-userspace utility as shown in examples below. See rzscontrol man page for more
-details.
-
-* Usage
-
-Following shows a typical sequence of steps for using ramzswap.
-
-1) Load Modules:
- modprobe ramzswap num_devices=4
- This creates 4 (uninitialized) devices: /dev/ramzswap{0,1,2,3}
- (num_devices parameter is optional. Default: 1)
-
-2) Initialize:
- Use rzscontrol utility to configure and initialize individual
- ramzswap devices. Example:
- rzscontrol /dev/ramzswap2 --init # uses default value of disksize_kb
-
- *See rzscontrol man page for more details and examples*
-
-3) Activate:
- swapon /dev/ramzswap2 # or any other initialized ramzswap device
-
-4) Stats:
- rzscontrol /dev/ramzswap2 --stats
-
-5) Deactivate:
- swapoff /dev/ramzswap2
-
-6) Reset:
- rzscontrol /dev/ramzswap2 --reset
- (This frees all the memory allocated for this device).
-
-
-Please report any problems at:
- - Mailing list: linux-mm-cc at laptop dot org
- - Issue tracker: http://code.google.com/p/compcache/issues/list
-
-Nitin Gupta
-ngupta@vflare.org
+++ /dev/null
-/*
- * 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");
+++ /dev/null
-/*
- * 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
- */
-
-#ifndef _RAMZSWAP_DRV_H_
-#define _RAMZSWAP_DRV_H_
-
-#include <linux/spinlock.h>
-#include <linux/mutex.h>
-
-#include "ramzswap_ioctl.h"
-#include "xvmalloc.h"
-
-/*
- * Some arbitrary value. This is just to catch
- * invalid value for num_devices module parameter.
- */
-static const unsigned max_num_devices = 32;
-
-/*
- * Stored at beginning of each compressed object.
- *
- * It stores back-reference to table entry which points to this
- * object. This is required to support memory defragmentation.
- */
-struct zobj_header {
-#if 0
- u32 table_idx;
-#endif
-};
-
-/*-- Configurable parameters */
-
-/* Default ramzswap disk size: 25% of total RAM */
-static const unsigned default_disksize_perc_ram = 25;
-
-/*
- * Pages that compress to size greater than this are stored
- * uncompressed in memory.
- */
-static const unsigned max_zpage_size = PAGE_SIZE / 4 * 3;
-
-/*
- * NOTE: max_zpage_size must be less than or equal to:
- * XV_MAX_ALLOC_SIZE - sizeof(struct zobj_header)
- * otherwise, xv_malloc() would always return failure.
- */
-
-/*-- End of configurable params */
-
-#define SECTOR_SHIFT 9
-#define SECTOR_SIZE (1 << SECTOR_SHIFT)
-#define SECTORS_PER_PAGE_SHIFT (PAGE_SHIFT - SECTOR_SHIFT)
-#define SECTORS_PER_PAGE (1 << SECTORS_PER_PAGE_SHIFT)
-
-/* Flags for ramzswap pages (table[page_no].flags) */
-enum rzs_pageflags {
- /* Page is stored uncompressed */
- RZS_UNCOMPRESSED,
-
- /* Page consists entirely of zeros */
- RZS_ZERO,
-
- __NR_RZS_PAGEFLAGS,
-};
-
-/*-- Data structures */
-
-/*
- * Allocated for each swap slot, indexed by page no.
- * These table entries must fit exactly in a page.
- */
-struct table {
- struct page *page;
- u16 offset;
- u8 count; /* object ref count (not yet used) */
- u8 flags;
-} __attribute__((aligned(4)));
-
-struct ramzswap_stats {
- /* basic stats */
- size_t compr_size; /* compressed size of pages stored -
- * needed to enforce memlimit */
- /* more stats */
-#if defined(CONFIG_RAMZSWAP_STATS)
- u64 num_reads; /* failed + successful */
- u64 num_writes; /* --do-- */
- u64 failed_reads; /* should NEVER! happen */
- u64 failed_writes; /* can happen when memory is too low */
- u64 invalid_io; /* non-swap I/O requests */
- u64 notify_free; /* no. of swap slot free notifications */
- u32 pages_zero; /* no. of zero filled pages */
- u32 pages_stored; /* no. of pages currently stored */
- u32 good_compress; /* % of pages with compression ratio<=50% */
- u32 pages_expand; /* % of incompressible pages */
-#endif
-};
-
-struct ramzswap {
- struct xv_pool *mem_pool;
- void *compress_workmem;
- void *compress_buffer;
- struct table *table;
- spinlock_t stat64_lock; /* protect 64-bit stats */
- struct mutex lock; /* protect compression buffers against
- * concurrent writes */
- struct request_queue *queue;
- struct gendisk *disk;
- int init_done;
- /*
- * This is limit on amount of *uncompressed* worth of data
- * we can hold. When backing swap device is provided, it is
- * set equal to device size.
- */
- size_t disksize; /* bytes */
-
- struct ramzswap_stats stats;
-};
-
-/*-- */
-
-/* Debugging and Stats */
-#if defined(CONFIG_RAMZSWAP_STATS)
-static void rzs_stat_inc(u32 *v)
-{
- *v = *v + 1;
-}
-
-static void rzs_stat_dec(u32 *v)
-{
- *v = *v - 1;
-}
-
-static void rzs_stat64_inc(struct ramzswap *rzs, u64 *v)
-{
- spin_lock(&rzs->stat64_lock);
- *v = *v + 1;
- spin_unlock(&rzs->stat64_lock);
-}
-
-static u64 rzs_stat64_read(struct ramzswap *rzs, u64 *v)
-{
- u64 val;
-
- spin_lock(&rzs->stat64_lock);
- val = *v;
- spin_unlock(&rzs->stat64_lock);
-
- return val;
-}
-#else
-#define rzs_stat_inc(v)
-#define rzs_stat_dec(v)
-#define rzs_stat64_inc(r, v)
-#define rzs_stat64_read(r, v)
-#endif /* CONFIG_RAMZSWAP_STATS */
-
-#endif
+++ /dev/null
-/*
- * 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
- */
-
-#ifndef _RAMZSWAP_IOCTL_H_
-#define _RAMZSWAP_IOCTL_H_
-
-struct ramzswap_ioctl_stats {
- u64 disksize; /* user specified or equal to backing swap
- * size (if present) */
- u64 num_reads; /* failed + successful */
- u64 num_writes; /* --do-- */
- u64 failed_reads; /* should NEVER! happen */
- u64 failed_writes; /* can happen when memory is too low */
- u64 invalid_io; /* non-swap I/O requests */
- u64 notify_free; /* no. of swap slot free notifications */
- u32 pages_zero; /* no. of zero filled pages */
- u32 good_compress_pct; /* no. of pages with compression ratio<=50% */
- u32 pages_expand_pct; /* no. of incompressible pages */
- u32 pages_stored;
- u32 pages_used;
- u64 orig_data_size;
- u64 compr_data_size;
- u64 mem_used_total;
-} __attribute__ ((packed, aligned(4)));
-
-#define RZSIO_SET_DISKSIZE_KB _IOW('z', 0, size_t)
-#define RZSIO_GET_STATS _IOR('z', 1, struct ramzswap_ioctl_stats)
-#define RZSIO_INIT _IO('z', 2)
-#define RZSIO_RESET _IO('z', 3)
-
-#endif
+++ /dev/null
-/*
- * xvmalloc memory allocator
- *
- * 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
- */
-
-#include <linux/bitops.h>
-#include <linux/errno.h>
-#include <linux/highmem.h>
-#include <linux/init.h>
-#include <linux/string.h>
-#include <linux/slab.h>
-
-#include "xvmalloc.h"
-#include "xvmalloc_int.h"
-
-static void stat_inc(u64 *value)
-{
- *value = *value + 1;
-}
-
-static void stat_dec(u64 *value)
-{
- *value = *value - 1;
-}
-
-static int test_flag(struct block_header *block, enum blockflags flag)
-{
- return block->prev & BIT(flag);
-}
-
-static void set_flag(struct block_header *block, enum blockflags flag)
-{
- block->prev |= BIT(flag);
-}
-
-static void clear_flag(struct block_header *block, enum blockflags flag)
-{
- block->prev &= ~BIT(flag);
-}
-
-/*
- * Given <page, offset> pair, provide a derefrencable pointer.
- * This is called from xv_malloc/xv_free path, so it
- * needs to be fast.
- */
-static void *get_ptr_atomic(struct page *page, u16 offset, enum km_type type)
-{
- unsigned char *base;
-
- base = kmap_atomic(page, type);
- return base + offset;
-}
-
-static void put_ptr_atomic(void *ptr, enum km_type type)
-{
- kunmap_atomic(ptr, type);
-}
-
-static u32 get_blockprev(struct block_header *block)
-{
- return block->prev & PREV_MASK;
-}
-
-static void set_blockprev(struct block_header *block, u16 new_offset)
-{
- block->prev = new_offset | (block->prev & FLAGS_MASK);
-}
-
-static struct block_header *BLOCK_NEXT(struct block_header *block)
-{
- return (struct block_header *)
- ((char *)block + block->size + XV_ALIGN);
-}
-
-/*
- * Get index of free list containing blocks of maximum size
- * which is less than or equal to given size.
- */
-static u32 get_index_for_insert(u32 size)
-{
- if (unlikely(size > XV_MAX_ALLOC_SIZE))
- size = XV_MAX_ALLOC_SIZE;
- size &= ~FL_DELTA_MASK;
- return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT;
-}
-
-/*
- * Get index of free list having blocks of size greater than
- * or equal to requested size.
- */
-static u32 get_index(u32 size)
-{
- if (unlikely(size < XV_MIN_ALLOC_SIZE))
- size = XV_MIN_ALLOC_SIZE;
- size = ALIGN(size, FL_DELTA);
- return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT;
-}
-
-/**
- * find_block - find block of at least given size
- * @pool: memory pool to search from
- * @size: size of block required
- * @page: page containing required block
- * @offset: offset within the page where block is located.
- *
- * Searches two level bitmap to locate block of at least
- * the given size. If such a block is found, it provides
- * <page, offset> to identify this block and returns index
- * in freelist where we found this block.
- * Otherwise, returns 0 and <page, offset> params are not touched.
- */
-static u32 find_block(struct xv_pool *pool, u32 size,
- struct page **page, u32 *offset)
-{
- ulong flbitmap, slbitmap;
- u32 flindex, slindex, slbitstart;
-
- /* There are no free blocks in this pool */
- if (!pool->flbitmap)
- return 0;
-
- /* Get freelist index correspoding to this size */
- slindex = get_index(size);
- slbitmap = pool->slbitmap[slindex / BITS_PER_LONG];
- slbitstart = slindex % BITS_PER_LONG;
-
- /*
- * If freelist is not empty at this index, we found the
- * block - head of this list. This is approximate best-fit match.
- */
- if (test_bit(slbitstart, &slbitmap)) {
- *page = pool->freelist[slindex].page;
- *offset = pool->freelist[slindex].offset;
- return slindex;
- }
-
- /*
- * No best-fit found. Search a bit further in bitmap for a free block.
- * Second level bitmap consists of series of 32-bit chunks. Search
- * further in the chunk where we expected a best-fit, starting from
- * index location found above.
- */
- slbitstart++;
- slbitmap >>= slbitstart;
-
- /* Skip this search if we were already at end of this bitmap chunk */
- if ((slbitstart != BITS_PER_LONG) && slbitmap) {
- slindex += __ffs(slbitmap) + 1;
- *page = pool->freelist[slindex].page;
- *offset = pool->freelist[slindex].offset;
- return slindex;
- }
-
- /* Now do a full two-level bitmap search to find next nearest fit */
- flindex = slindex / BITS_PER_LONG;
-
- flbitmap = (pool->flbitmap) >> (flindex + 1);
- if (!flbitmap)
- return 0;
-
- flindex += __ffs(flbitmap) + 1;
- slbitmap = pool->slbitmap[flindex];
- slindex = (flindex * BITS_PER_LONG) + __ffs(slbitmap);
- *page = pool->freelist[slindex].page;
- *offset = pool->freelist[slindex].offset;
-
- return slindex;
-}
-
-/*
- * Insert block at <page, offset> in freelist of given pool.
- * freelist used depends on block size.
- */
-static void insert_block(struct xv_pool *pool, struct page *page, u32 offset,
- struct block_header *block)
-{
- u32 flindex, slindex;
- struct block_header *nextblock;
-
- slindex = get_index_for_insert(block->size);
- flindex = slindex / BITS_PER_LONG;
-
- block->link.prev_page = 0;
- block->link.prev_offset = 0;
- block->link.next_page = pool->freelist[slindex].page;
- block->link.next_offset = pool->freelist[slindex].offset;
- pool->freelist[slindex].page = page;
- pool->freelist[slindex].offset = offset;
-
- if (block->link.next_page) {
- nextblock = get_ptr_atomic(block->link.next_page,
- block->link.next_offset, KM_USER1);
- nextblock->link.prev_page = page;
- nextblock->link.prev_offset = offset;
- put_ptr_atomic(nextblock, KM_USER1);
- }
-
- __set_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]);
- __set_bit(flindex, &pool->flbitmap);
-}
-
-/*
- * Remove block from head of freelist. Index 'slindex' identifies the freelist.
- */
-static void remove_block_head(struct xv_pool *pool,
- struct block_header *block, u32 slindex)
-{
- struct block_header *tmpblock;
- u32 flindex = slindex / BITS_PER_LONG;
-
- pool->freelist[slindex].page = block->link.next_page;
- pool->freelist[slindex].offset = block->link.next_offset;
- block->link.prev_page = 0;
- block->link.prev_offset = 0;
-
- if (!pool->freelist[slindex].page) {
- __clear_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]);
- if (!pool->slbitmap[flindex])
- __clear_bit(flindex, &pool->flbitmap);
- } else {
- /*
- * DEBUG ONLY: We need not reinitialize freelist head previous
- * pointer to 0 - we never depend on its value. But just for
- * sanity, lets do it.
- */
- tmpblock = get_ptr_atomic(pool->freelist[slindex].page,
- pool->freelist[slindex].offset, KM_USER1);
- tmpblock->link.prev_page = 0;
- tmpblock->link.prev_offset = 0;
- put_ptr_atomic(tmpblock, KM_USER1);
- }
-}
-
-/*
- * Remove block from freelist. Index 'slindex' identifies the freelist.
- */
-static void remove_block(struct xv_pool *pool, struct page *page, u32 offset,
- struct block_header *block, u32 slindex)
-{
- u32 flindex;
- struct block_header *tmpblock;
-
- if (pool->freelist[slindex].page == page
- && pool->freelist[slindex].offset == offset) {
- remove_block_head(pool, block, slindex);
- return;
- }
-
- flindex = slindex / BITS_PER_LONG;
-
- if (block->link.prev_page) {
- tmpblock = get_ptr_atomic(block->link.prev_page,
- block->link.prev_offset, KM_USER1);
- tmpblock->link.next_page = block->link.next_page;
- tmpblock->link.next_offset = block->link.next_offset;
- put_ptr_atomic(tmpblock, KM_USER1);
- }
-
- if (block->link.next_page) {
- tmpblock = get_ptr_atomic(block->link.next_page,
- block->link.next_offset, KM_USER1);
- tmpblock->link.prev_page = block->link.prev_page;
- tmpblock->link.prev_offset = block->link.prev_offset;
- put_ptr_atomic(tmpblock, KM_USER1);
- }
-}
-
-/*
- * Allocate a page and add it to freelist of given pool.
- */
-static int grow_pool(struct xv_pool *pool, gfp_t flags)
-{
- struct page *page;
- struct block_header *block;
-
- page = alloc_page(flags);
- if (unlikely(!page))
- return -ENOMEM;
-
- stat_inc(&pool->total_pages);
-
- spin_lock(&pool->lock);
- block = get_ptr_atomic(page, 0, KM_USER0);
-
- block->size = PAGE_SIZE - XV_ALIGN;
- set_flag(block, BLOCK_FREE);
- clear_flag(block, PREV_FREE);
- set_blockprev(block, 0);
-
- insert_block(pool, page, 0, block);
-
- put_ptr_atomic(block, KM_USER0);
- spin_unlock(&pool->lock);
-
- return 0;
-}
-
-/*
- * Create a memory pool. Allocates freelist, bitmaps and other
- * per-pool metadata.
- */
-struct xv_pool *xv_create_pool(void)
-{
- u32 ovhd_size;
- struct xv_pool *pool;
-
- ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
- pool = kzalloc(ovhd_size, GFP_KERNEL);
- if (!pool)
- return NULL;
-
- spin_lock_init(&pool->lock);
-
- return pool;
-}
-
-void xv_destroy_pool(struct xv_pool *pool)
-{
- kfree(pool);
-}
-
-/**
- * xv_malloc - Allocate block of given size from pool.
- * @pool: pool to allocate from
- * @size: size of block to allocate
- * @page: page no. that holds the object
- * @offset: location of object within page
- *
- * On success, <page, offset> identifies block allocated
- * and 0 is returned. On failure, <page, offset> is set to
- * 0 and -ENOMEM is returned.
- *
- * Allocation requests with size > XV_MAX_ALLOC_SIZE will fail.
- */
-int xv_malloc(struct xv_pool *pool, u32 size, struct page **page,
- u32 *offset, gfp_t flags)
-{
- int error;
- u32 index, tmpsize, origsize, tmpoffset;
- struct block_header *block, *tmpblock;
-
- *page = NULL;
- *offset = 0;
- origsize = size;
-
- if (unlikely(!size || size > XV_MAX_ALLOC_SIZE))
- return -ENOMEM;
-
- size = ALIGN(size, XV_ALIGN);
-
- spin_lock(&pool->lock);
-
- index = find_block(pool, size, page, offset);
-
- if (!*page) {
- spin_unlock(&pool->lock);
- if (flags & GFP_NOWAIT)
- return -ENOMEM;
- error = grow_pool(pool, flags);
- if (unlikely(error))
- return error;
-
- spin_lock(&pool->lock);
- index = find_block(pool, size, page, offset);
- }
-
- if (!*page) {
- spin_unlock(&pool->lock);
- return -ENOMEM;
- }
-
- block = get_ptr_atomic(*page, *offset, KM_USER0);
-
- remove_block_head(pool, block, index);
-
- /* Split the block if required */
- tmpoffset = *offset + size + XV_ALIGN;
- tmpsize = block->size - size;
- tmpblock = (struct block_header *)((char *)block + size + XV_ALIGN);
- if (tmpsize) {
- tmpblock->size = tmpsize - XV_ALIGN;
- set_flag(tmpblock, BLOCK_FREE);
- clear_flag(tmpblock, PREV_FREE);
-
- set_blockprev(tmpblock, *offset);
- if (tmpblock->size >= XV_MIN_ALLOC_SIZE)
- insert_block(pool, *page, tmpoffset, tmpblock);
-
- if (tmpoffset + XV_ALIGN + tmpblock->size != PAGE_SIZE) {
- tmpblock = BLOCK_NEXT(tmpblock);
- set_blockprev(tmpblock, tmpoffset);
- }
- } else {
- /* This block is exact fit */
- if (tmpoffset != PAGE_SIZE)
- clear_flag(tmpblock, PREV_FREE);
- }
-
- block->size = origsize;
- clear_flag(block, BLOCK_FREE);
-
- put_ptr_atomic(block, KM_USER0);
- spin_unlock(&pool->lock);
-
- *offset += XV_ALIGN;
-
- return 0;
-}
-
-/*
- * Free block identified with <page, offset>
- */
-void xv_free(struct xv_pool *pool, struct page *page, u32 offset)
-{
- void *page_start;
- struct block_header *block, *tmpblock;
-
- offset -= XV_ALIGN;
-
- spin_lock(&pool->lock);
-
- page_start = get_ptr_atomic(page, 0, KM_USER0);
- block = (struct block_header *)((char *)page_start + offset);
-
- /* Catch double free bugs */
- BUG_ON(test_flag(block, BLOCK_FREE));
-
- block->size = ALIGN(block->size, XV_ALIGN);
-
- tmpblock = BLOCK_NEXT(block);
- if (offset + block->size + XV_ALIGN == PAGE_SIZE)
- tmpblock = NULL;
-
- /* Merge next block if its free */
- if (tmpblock && test_flag(tmpblock, BLOCK_FREE)) {
- /*
- * Blocks smaller than XV_MIN_ALLOC_SIZE
- * are not inserted in any free list.
- */
- if (tmpblock->size >= XV_MIN_ALLOC_SIZE) {
- remove_block(pool, page,
- offset + block->size + XV_ALIGN, tmpblock,
- get_index_for_insert(tmpblock->size));
- }
- block->size += tmpblock->size + XV_ALIGN;
- }
-
- /* Merge previous block if its free */
- if (test_flag(block, PREV_FREE)) {
- tmpblock = (struct block_header *)((char *)(page_start) +
- get_blockprev(block));
- offset = offset - tmpblock->size - XV_ALIGN;
-
- if (tmpblock->size >= XV_MIN_ALLOC_SIZE)
- remove_block(pool, page, offset, tmpblock,
- get_index_for_insert(tmpblock->size));
-
- tmpblock->size += block->size + XV_ALIGN;
- block = tmpblock;
- }
-
- /* No used objects in this page. Free it. */
- if (block->size == PAGE_SIZE - XV_ALIGN) {
- put_ptr_atomic(page_start, KM_USER0);
- spin_unlock(&pool->lock);
-
- __free_page(page);
- stat_dec(&pool->total_pages);
- return;
- }
-
- set_flag(block, BLOCK_FREE);
- if (block->size >= XV_MIN_ALLOC_SIZE)
- insert_block(pool, page, offset, block);
-
- if (offset + block->size + XV_ALIGN != PAGE_SIZE) {
- tmpblock = BLOCK_NEXT(block);
- set_flag(tmpblock, PREV_FREE);
- set_blockprev(tmpblock, offset);
- }
-
- put_ptr_atomic(page_start, KM_USER0);
- spin_unlock(&pool->lock);
-}
-
-u32 xv_get_object_size(void *obj)
-{
- struct block_header *blk;
-
- blk = (struct block_header *)((char *)(obj) - XV_ALIGN);
- return blk->size;
-}
-
-/*
- * Returns total memory used by allocator (userdata + metadata)
- */
-u64 xv_get_total_size_bytes(struct xv_pool *pool)
-{
- return pool->total_pages << PAGE_SHIFT;
-}
+++ /dev/null
-/*
- * xvmalloc memory allocator
- *
- * 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
- */
-
-#ifndef _XV_MALLOC_H_
-#define _XV_MALLOC_H_
-
-#include <linux/types.h>
-
-struct xv_pool;
-
-struct xv_pool *xv_create_pool(void);
-void xv_destroy_pool(struct xv_pool *pool);
-
-int xv_malloc(struct xv_pool *pool, u32 size, struct page **page,
- u32 *offset, gfp_t flags);
-void xv_free(struct xv_pool *pool, struct page *page, u32 offset);
-
-u32 xv_get_object_size(void *obj);
-u64 xv_get_total_size_bytes(struct xv_pool *pool);
-
-#endif
+++ /dev/null
-/*
- * xvmalloc memory allocator
- *
- * 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
- */
-
-#ifndef _XV_MALLOC_INT_H_
-#define _XV_MALLOC_INT_H_
-
-#include <linux/kernel.h>
-#include <linux/types.h>
-
-/* User configurable params */
-
-/* Must be power of two */
-#define XV_ALIGN_SHIFT 2
-#define XV_ALIGN (1 << XV_ALIGN_SHIFT)
-#define XV_ALIGN_MASK (XV_ALIGN - 1)
-
-/* This must be greater than sizeof(link_free) */
-#define XV_MIN_ALLOC_SIZE 32
-#define XV_MAX_ALLOC_SIZE (PAGE_SIZE - XV_ALIGN)
-
-/* Free lists are separated by FL_DELTA bytes */
-#define FL_DELTA_SHIFT 3
-#define FL_DELTA (1 << FL_DELTA_SHIFT)
-#define FL_DELTA_MASK (FL_DELTA - 1)
-#define NUM_FREE_LISTS ((XV_MAX_ALLOC_SIZE - XV_MIN_ALLOC_SIZE) \
- / FL_DELTA + 1)
-
-#define MAX_FLI DIV_ROUND_UP(NUM_FREE_LISTS, BITS_PER_LONG)
-
-/* End of user params */
-
-enum blockflags {
- BLOCK_FREE,
- PREV_FREE,
- __NR_BLOCKFLAGS,
-};
-
-#define FLAGS_MASK XV_ALIGN_MASK
-#define PREV_MASK (~FLAGS_MASK)
-
-struct freelist_entry {
- struct page *page;
- u16 offset;
- u16 pad;
-};
-
-struct link_free {
- struct page *prev_page;
- struct page *next_page;
- u16 prev_offset;
- u16 next_offset;
-};
-
-struct block_header {
- union {
- /* This common header must be XV_ALIGN bytes */
- u8 common[XV_ALIGN];
- struct {
- u16 size;
- u16 prev;
- };
- };
- struct link_free link;
-};
-
-struct xv_pool {
- ulong flbitmap;
- ulong slbitmap[MAX_FLI];
- spinlock_t lock;
-
- struct freelist_entry freelist[NUM_FREE_LISTS];
-
- /* stats */
- u64 total_pages;
-};
-
-#endif
--- /dev/null
+config ZRAM
+ tristate "Compressed RAM block device support"
+ select LZO_COMPRESS
+ select LZO_DECOMPRESS
+ default n
+ help
+ Creates virtual block devices called /dev/zramX (X = 0, 1, ...).
+ Pages written to these disks are compressed and stored in memory
+ itself. These disks allow very fast I/O and compression provides
+ good amounts of memory savings.
+
+ It has several use cases, for example: /tmp storage, use as swap
+ disks and maybe many more.
+
+ See zram.txt for more information.
+ Project home: http://compcache.googlecode.com/
+
+config ZRAM_STATS
+ bool "Enable statistics for compressed RAM disks"
+ depends on ZRAM
+ default y
+ help
+ Enable statistics collection for compressed RAM devices. Statistics
+ are exported through ioctl interface, so you have to use zramconfig
+ program to get them. This adds only a minimal overhead.
+
+ If unsure, say Y.
+
--- /dev/null
+zram-objs := zram_drv.o xvmalloc.o
+
+obj-$(CONFIG_ZRAM) += zram.o
--- /dev/null
+/*
+ * xvmalloc memory allocator
+ *
+ * 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
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/highmem.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+#include "xvmalloc.h"
+#include "xvmalloc_int.h"
+
+static void stat_inc(u64 *value)
+{
+ *value = *value + 1;
+}
+
+static void stat_dec(u64 *value)
+{
+ *value = *value - 1;
+}
+
+static int test_flag(struct block_header *block, enum blockflags flag)
+{
+ return block->prev & BIT(flag);
+}
+
+static void set_flag(struct block_header *block, enum blockflags flag)
+{
+ block->prev |= BIT(flag);
+}
+
+static void clear_flag(struct block_header *block, enum blockflags flag)
+{
+ block->prev &= ~BIT(flag);
+}
+
+/*
+ * Given <page, offset> pair, provide a derefrencable pointer.
+ * This is called from xv_malloc/xv_free path, so it
+ * needs to be fast.
+ */
+static void *get_ptr_atomic(struct page *page, u16 offset, enum km_type type)
+{
+ unsigned char *base;
+
+ base = kmap_atomic(page, type);
+ return base + offset;
+}
+
+static void put_ptr_atomic(void *ptr, enum km_type type)
+{
+ kunmap_atomic(ptr, type);
+}
+
+static u32 get_blockprev(struct block_header *block)
+{
+ return block->prev & PREV_MASK;
+}
+
+static void set_blockprev(struct block_header *block, u16 new_offset)
+{
+ block->prev = new_offset | (block->prev & FLAGS_MASK);
+}
+
+static struct block_header *BLOCK_NEXT(struct block_header *block)
+{
+ return (struct block_header *)
+ ((char *)block + block->size + XV_ALIGN);
+}
+
+/*
+ * Get index of free list containing blocks of maximum size
+ * which is less than or equal to given size.
+ */
+static u32 get_index_for_insert(u32 size)
+{
+ if (unlikely(size > XV_MAX_ALLOC_SIZE))
+ size = XV_MAX_ALLOC_SIZE;
+ size &= ~FL_DELTA_MASK;
+ return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT;
+}
+
+/*
+ * Get index of free list having blocks of size greater than
+ * or equal to requested size.
+ */
+static u32 get_index(u32 size)
+{
+ if (unlikely(size < XV_MIN_ALLOC_SIZE))
+ size = XV_MIN_ALLOC_SIZE;
+ size = ALIGN(size, FL_DELTA);
+ return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT;
+}
+
+/**
+ * find_block - find block of at least given size
+ * @pool: memory pool to search from
+ * @size: size of block required
+ * @page: page containing required block
+ * @offset: offset within the page where block is located.
+ *
+ * Searches two level bitmap to locate block of at least
+ * the given size. If such a block is found, it provides
+ * <page, offset> to identify this block and returns index
+ * in freelist where we found this block.
+ * Otherwise, returns 0 and <page, offset> params are not touched.
+ */
+static u32 find_block(struct xv_pool *pool, u32 size,
+ struct page **page, u32 *offset)
+{
+ ulong flbitmap, slbitmap;
+ u32 flindex, slindex, slbitstart;
+
+ /* There are no free blocks in this pool */
+ if (!pool->flbitmap)
+ return 0;
+
+ /* Get freelist index correspoding to this size */
+ slindex = get_index(size);
+ slbitmap = pool->slbitmap[slindex / BITS_PER_LONG];
+ slbitstart = slindex % BITS_PER_LONG;
+
+ /*
+ * If freelist is not empty at this index, we found the
+ * block - head of this list. This is approximate best-fit match.
+ */
+ if (test_bit(slbitstart, &slbitmap)) {
+ *page = pool->freelist[slindex].page;
+ *offset = pool->freelist[slindex].offset;
+ return slindex;
+ }
+
+ /*
+ * No best-fit found. Search a bit further in bitmap for a free block.
+ * Second level bitmap consists of series of 32-bit chunks. Search
+ * further in the chunk where we expected a best-fit, starting from
+ * index location found above.
+ */
+ slbitstart++;
+ slbitmap >>= slbitstart;
+
+ /* Skip this search if we were already at end of this bitmap chunk */
+ if ((slbitstart != BITS_PER_LONG) && slbitmap) {
+ slindex += __ffs(slbitmap) + 1;
+ *page = pool->freelist[slindex].page;
+ *offset = pool->freelist[slindex].offset;
+ return slindex;
+ }
+
+ /* Now do a full two-level bitmap search to find next nearest fit */
+ flindex = slindex / BITS_PER_LONG;
+
+ flbitmap = (pool->flbitmap) >> (flindex + 1);
+ if (!flbitmap)
+ return 0;
+
+ flindex += __ffs(flbitmap) + 1;
+ slbitmap = pool->slbitmap[flindex];
+ slindex = (flindex * BITS_PER_LONG) + __ffs(slbitmap);
+ *page = pool->freelist[slindex].page;
+ *offset = pool->freelist[slindex].offset;
+
+ return slindex;
+}
+
+/*
+ * Insert block at <page, offset> in freelist of given pool.
+ * freelist used depends on block size.
+ */
+static void insert_block(struct xv_pool *pool, struct page *page, u32 offset,
+ struct block_header *block)
+{
+ u32 flindex, slindex;
+ struct block_header *nextblock;
+
+ slindex = get_index_for_insert(block->size);
+ flindex = slindex / BITS_PER_LONG;
+
+ block->link.prev_page = 0;
+ block->link.prev_offset = 0;
+ block->link.next_page = pool->freelist[slindex].page;
+ block->link.next_offset = pool->freelist[slindex].offset;
+ pool->freelist[slindex].page = page;
+ pool->freelist[slindex].offset = offset;
+
+ if (block->link.next_page) {
+ nextblock = get_ptr_atomic(block->link.next_page,
+ block->link.next_offset, KM_USER1);
+ nextblock->link.prev_page = page;
+ nextblock->link.prev_offset = offset;
+ put_ptr_atomic(nextblock, KM_USER1);
+ }
+
+ __set_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]);
+ __set_bit(flindex, &pool->flbitmap);
+}
+
+/*
+ * Remove block from head of freelist. Index 'slindex' identifies the freelist.
+ */
+static void remove_block_head(struct xv_pool *pool,
+ struct block_header *block, u32 slindex)
+{
+ struct block_header *tmpblock;
+ u32 flindex = slindex / BITS_PER_LONG;
+
+ pool->freelist[slindex].page = block->link.next_page;
+ pool->freelist[slindex].offset = block->link.next_offset;
+ block->link.prev_page = 0;
+ block->link.prev_offset = 0;
+
+ if (!pool->freelist[slindex].page) {
+ __clear_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]);
+ if (!pool->slbitmap[flindex])
+ __clear_bit(flindex, &pool->flbitmap);
+ } else {
+ /*
+ * DEBUG ONLY: We need not reinitialize freelist head previous
+ * pointer to 0 - we never depend on its value. But just for
+ * sanity, lets do it.
+ */
+ tmpblock = get_ptr_atomic(pool->freelist[slindex].page,
+ pool->freelist[slindex].offset, KM_USER1);
+ tmpblock->link.prev_page = 0;
+ tmpblock->link.prev_offset = 0;
+ put_ptr_atomic(tmpblock, KM_USER1);
+ }
+}
+
+/*
+ * Remove block from freelist. Index 'slindex' identifies the freelist.
+ */
+static void remove_block(struct xv_pool *pool, struct page *page, u32 offset,
+ struct block_header *block, u32 slindex)
+{
+ u32 flindex;
+ struct block_header *tmpblock;
+
+ if (pool->freelist[slindex].page == page
+ && pool->freelist[slindex].offset == offset) {
+ remove_block_head(pool, block, slindex);
+ return;
+ }
+
+ flindex = slindex / BITS_PER_LONG;
+
+ if (block->link.prev_page) {
+ tmpblock = get_ptr_atomic(block->link.prev_page,
+ block->link.prev_offset, KM_USER1);
+ tmpblock->link.next_page = block->link.next_page;
+ tmpblock->link.next_offset = block->link.next_offset;
+ put_ptr_atomic(tmpblock, KM_USER1);
+ }
+
+ if (block->link.next_page) {
+ tmpblock = get_ptr_atomic(block->link.next_page,
+ block->link.next_offset, KM_USER1);
+ tmpblock->link.prev_page = block->link.prev_page;
+ tmpblock->link.prev_offset = block->link.prev_offset;
+ put_ptr_atomic(tmpblock, KM_USER1);
+ }
+}
+
+/*
+ * Allocate a page and add it to freelist of given pool.
+ */
+static int grow_pool(struct xv_pool *pool, gfp_t flags)
+{
+ struct page *page;
+ struct block_header *block;
+
+ page = alloc_page(flags);
+ if (unlikely(!page))
+ return -ENOMEM;
+
+ stat_inc(&pool->total_pages);
+
+ spin_lock(&pool->lock);
+ block = get_ptr_atomic(page, 0, KM_USER0);
+
+ block->size = PAGE_SIZE - XV_ALIGN;
+ set_flag(block, BLOCK_FREE);
+ clear_flag(block, PREV_FREE);
+ set_blockprev(block, 0);
+
+ insert_block(pool, page, 0, block);
+
+ put_ptr_atomic(block, KM_USER0);
+ spin_unlock(&pool->lock);
+
+ return 0;
+}
+
+/*
+ * Create a memory pool. Allocates freelist, bitmaps and other
+ * per-pool metadata.
+ */
+struct xv_pool *xv_create_pool(void)
+{
+ u32 ovhd_size;
+ struct xv_pool *pool;
+
+ ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
+ pool = kzalloc(ovhd_size, GFP_KERNEL);
+ if (!pool)
+ return NULL;
+
+ spin_lock_init(&pool->lock);
+
+ return pool;
+}
+
+void xv_destroy_pool(struct xv_pool *pool)
+{
+ kfree(pool);
+}
+
+/**
+ * xv_malloc - Allocate block of given size from pool.
+ * @pool: pool to allocate from
+ * @size: size of block to allocate
+ * @page: page no. that holds the object
+ * @offset: location of object within page
+ *
+ * On success, <page, offset> identifies block allocated
+ * and 0 is returned. On failure, <page, offset> is set to
+ * 0 and -ENOMEM is returned.
+ *
+ * Allocation requests with size > XV_MAX_ALLOC_SIZE will fail.
+ */
+int xv_malloc(struct xv_pool *pool, u32 size, struct page **page,
+ u32 *offset, gfp_t flags)
+{
+ int error;
+ u32 index, tmpsize, origsize, tmpoffset;
+ struct block_header *block, *tmpblock;
+
+ *page = NULL;
+ *offset = 0;
+ origsize = size;
+
+ if (unlikely(!size || size > XV_MAX_ALLOC_SIZE))
+ return -ENOMEM;
+
+ size = ALIGN(size, XV_ALIGN);
+
+ spin_lock(&pool->lock);
+
+ index = find_block(pool, size, page, offset);
+
+ if (!*page) {
+ spin_unlock(&pool->lock);
+ if (flags & GFP_NOWAIT)
+ return -ENOMEM;
+ error = grow_pool(pool, flags);
+ if (unlikely(error))
+ return error;
+
+ spin_lock(&pool->lock);
+ index = find_block(pool, size, page, offset);
+ }
+
+ if (!*page) {
+ spin_unlock(&pool->lock);
+ return -ENOMEM;
+ }
+
+ block = get_ptr_atomic(*page, *offset, KM_USER0);
+
+ remove_block_head(pool, block, index);
+
+ /* Split the block if required */
+ tmpoffset = *offset + size + XV_ALIGN;
+ tmpsize = block->size - size;
+ tmpblock = (struct block_header *)((char *)block + size + XV_ALIGN);
+ if (tmpsize) {
+ tmpblock->size = tmpsize - XV_ALIGN;
+ set_flag(tmpblock, BLOCK_FREE);
+ clear_flag(tmpblock, PREV_FREE);
+
+ set_blockprev(tmpblock, *offset);
+ if (tmpblock->size >= XV_MIN_ALLOC_SIZE)
+ insert_block(pool, *page, tmpoffset, tmpblock);
+
+ if (tmpoffset + XV_ALIGN + tmpblock->size != PAGE_SIZE) {
+ tmpblock = BLOCK_NEXT(tmpblock);
+ set_blockprev(tmpblock, tmpoffset);
+ }
+ } else {
+ /* This block is exact fit */
+ if (tmpoffset != PAGE_SIZE)
+ clear_flag(tmpblock, PREV_FREE);
+ }
+
+ block->size = origsize;
+ clear_flag(block, BLOCK_FREE);
+
+ put_ptr_atomic(block, KM_USER0);
+ spin_unlock(&pool->lock);
+
+ *offset += XV_ALIGN;
+
+ return 0;
+}
+
+/*
+ * Free block identified with <page, offset>
+ */
+void xv_free(struct xv_pool *pool, struct page *page, u32 offset)
+{
+ void *page_start;
+ struct block_header *block, *tmpblock;
+
+ offset -= XV_ALIGN;
+
+ spin_lock(&pool->lock);
+
+ page_start = get_ptr_atomic(page, 0, KM_USER0);
+ block = (struct block_header *)((char *)page_start + offset);
+
+ /* Catch double free bugs */
+ BUG_ON(test_flag(block, BLOCK_FREE));
+
+ block->size = ALIGN(block->size, XV_ALIGN);
+
+ tmpblock = BLOCK_NEXT(block);
+ if (offset + block->size + XV_ALIGN == PAGE_SIZE)
+ tmpblock = NULL;
+
+ /* Merge next block if its free */
+ if (tmpblock && test_flag(tmpblock, BLOCK_FREE)) {
+ /*
+ * Blocks smaller than XV_MIN_ALLOC_SIZE
+ * are not inserted in any free list.
+ */
+ if (tmpblock->size >= XV_MIN_ALLOC_SIZE) {
+ remove_block(pool, page,
+ offset + block->size + XV_ALIGN, tmpblock,
+ get_index_for_insert(tmpblock->size));
+ }
+ block->size += tmpblock->size + XV_ALIGN;
+ }
+
+ /* Merge previous block if its free */
+ if (test_flag(block, PREV_FREE)) {
+ tmpblock = (struct block_header *)((char *)(page_start) +
+ get_blockprev(block));
+ offset = offset - tmpblock->size - XV_ALIGN;
+
+ if (tmpblock->size >= XV_MIN_ALLOC_SIZE)
+ remove_block(pool, page, offset, tmpblock,
+ get_index_for_insert(tmpblock->size));
+
+ tmpblock->size += block->size + XV_ALIGN;
+ block = tmpblock;
+ }
+
+ /* No used objects in this page. Free it. */
+ if (block->size == PAGE_SIZE - XV_ALIGN) {
+ put_ptr_atomic(page_start, KM_USER0);
+ spin_unlock(&pool->lock);
+
+ __free_page(page);
+ stat_dec(&pool->total_pages);
+ return;
+ }
+
+ set_flag(block, BLOCK_FREE);
+ if (block->size >= XV_MIN_ALLOC_SIZE)
+ insert_block(pool, page, offset, block);
+
+ if (offset + block->size + XV_ALIGN != PAGE_SIZE) {
+ tmpblock = BLOCK_NEXT(block);
+ set_flag(tmpblock, PREV_FREE);
+ set_blockprev(tmpblock, offset);
+ }
+
+ put_ptr_atomic(page_start, KM_USER0);
+ spin_unlock(&pool->lock);
+}
+
+u32 xv_get_object_size(void *obj)
+{
+ struct block_header *blk;
+
+ blk = (struct block_header *)((char *)(obj) - XV_ALIGN);
+ return blk->size;
+}
+
+/*
+ * Returns total memory used by allocator (userdata + metadata)
+ */
+u64 xv_get_total_size_bytes(struct xv_pool *pool)
+{
+ return pool->total_pages << PAGE_SHIFT;
+}
--- /dev/null
+/*
+ * xvmalloc memory allocator
+ *
+ * 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
+ */
+
+#ifndef _XV_MALLOC_H_
+#define _XV_MALLOC_H_
+
+#include <linux/types.h>
+
+struct xv_pool;
+
+struct xv_pool *xv_create_pool(void);
+void xv_destroy_pool(struct xv_pool *pool);
+
+int xv_malloc(struct xv_pool *pool, u32 size, struct page **page,
+ u32 *offset, gfp_t flags);
+void xv_free(struct xv_pool *pool, struct page *page, u32 offset);
+
+u32 xv_get_object_size(void *obj);
+u64 xv_get_total_size_bytes(struct xv_pool *pool);
+
+#endif
--- /dev/null
+/*
+ * xvmalloc memory allocator
+ *
+ * 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
+ */
+
+#ifndef _XV_MALLOC_INT_H_
+#define _XV_MALLOC_INT_H_
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+/* User configurable params */
+
+/* Must be power of two */
+#define XV_ALIGN_SHIFT 2
+#define XV_ALIGN (1 << XV_ALIGN_SHIFT)
+#define XV_ALIGN_MASK (XV_ALIGN - 1)
+
+/* This must be greater than sizeof(link_free) */
+#define XV_MIN_ALLOC_SIZE 32
+#define XV_MAX_ALLOC_SIZE (PAGE_SIZE - XV_ALIGN)
+
+/* Free lists are separated by FL_DELTA bytes */
+#define FL_DELTA_SHIFT 3
+#define FL_DELTA (1 << FL_DELTA_SHIFT)
+#define FL_DELTA_MASK (FL_DELTA - 1)
+#define NUM_FREE_LISTS ((XV_MAX_ALLOC_SIZE - XV_MIN_ALLOC_SIZE) \
+ / FL_DELTA + 1)
+
+#define MAX_FLI DIV_ROUND_UP(NUM_FREE_LISTS, BITS_PER_LONG)
+
+/* End of user params */
+
+enum blockflags {
+ BLOCK_FREE,
+ PREV_FREE,
+ __NR_BLOCKFLAGS,
+};
+
+#define FLAGS_MASK XV_ALIGN_MASK
+#define PREV_MASK (~FLAGS_MASK)
+
+struct freelist_entry {
+ struct page *page;
+ u16 offset;
+ u16 pad;
+};
+
+struct link_free {
+ struct page *prev_page;
+ struct page *next_page;
+ u16 prev_offset;
+ u16 next_offset;
+};
+
+struct block_header {
+ union {
+ /* This common header must be XV_ALIGN bytes */
+ u8 common[XV_ALIGN];
+ struct {
+ u16 size;
+ u16 prev;
+ };
+ };
+ struct link_free link;
+};
+
+struct xv_pool {
+ ulong flbitmap;
+ ulong slbitmap[MAX_FLI];
+ spinlock_t lock;
+
+ struct freelist_entry freelist[NUM_FREE_LISTS];
+
+ /* stats */
+ u64 total_pages;
+};
+
+#endif
--- /dev/null
+ramzswap: Compressed RAM based swap device
+-------------------------------------------
+
+Project home: http://compcache.googlecode.com/
+
+* Introduction
+
+The ramzswap module creates RAM based block devices which can (only) be used as
+swap disks. Pages swapped to these devices are compressed and stored in memory
+itself. See project home for use cases, performance numbers and a lot more.
+
+Individual ramzswap devices are configured and initialized using rzscontrol
+userspace utility as shown in examples below. See rzscontrol man page for more
+details.
+
+* Usage
+
+Following shows a typical sequence of steps for using ramzswap.
+
+1) Load Modules:
+ modprobe ramzswap num_devices=4
+ This creates 4 (uninitialized) devices: /dev/ramzswap{0,1,2,3}
+ (num_devices parameter is optional. Default: 1)
+
+2) Initialize:
+ Use rzscontrol utility to configure and initialize individual
+ ramzswap devices. Example:
+ rzscontrol /dev/ramzswap2 --init # uses default value of disksize_kb
+
+ *See rzscontrol man page for more details and examples*
+
+3) Activate:
+ swapon /dev/ramzswap2 # or any other initialized ramzswap device
+
+4) Stats:
+ rzscontrol /dev/ramzswap2 --stats
+
+5) Deactivate:
+ swapoff /dev/ramzswap2
+
+6) Reset:
+ rzscontrol /dev/ramzswap2 --reset
+ (This frees all the memory allocated for this device).
+
+
+Please report any problems at:
+ - Mailing list: linux-mm-cc at laptop dot org
+ - Issue tracker: http://code.google.com/p/compcache/issues/list
+
+Nitin Gupta
+ngupta@vflare.org
--- /dev/null
+/*
+ * 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 "zram_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");
--- /dev/null
+/*
+ * 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
+ */
+
+#ifndef _RAMZSWAP_DRV_H_
+#define _RAMZSWAP_DRV_H_
+
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+
+#include "zram_ioctl.h"
+#include "xvmalloc.h"
+
+/*
+ * Some arbitrary value. This is just to catch
+ * invalid value for num_devices module parameter.
+ */
+static const unsigned max_num_devices = 32;
+
+/*
+ * Stored at beginning of each compressed object.
+ *
+ * It stores back-reference to table entry which points to this
+ * object. This is required to support memory defragmentation.
+ */
+struct zobj_header {
+#if 0
+ u32 table_idx;
+#endif
+};
+
+/*-- Configurable parameters */
+
+/* Default ramzswap disk size: 25% of total RAM */
+static const unsigned default_disksize_perc_ram = 25;
+
+/*
+ * Pages that compress to size greater than this are stored
+ * uncompressed in memory.
+ */
+static const unsigned max_zpage_size = PAGE_SIZE / 4 * 3;
+
+/*
+ * NOTE: max_zpage_size must be less than or equal to:
+ * XV_MAX_ALLOC_SIZE - sizeof(struct zobj_header)
+ * otherwise, xv_malloc() would always return failure.
+ */
+
+/*-- End of configurable params */
+
+#define SECTOR_SHIFT 9
+#define SECTOR_SIZE (1 << SECTOR_SHIFT)
+#define SECTORS_PER_PAGE_SHIFT (PAGE_SHIFT - SECTOR_SHIFT)
+#define SECTORS_PER_PAGE (1 << SECTORS_PER_PAGE_SHIFT)
+
+/* Flags for ramzswap pages (table[page_no].flags) */
+enum rzs_pageflags {
+ /* Page is stored uncompressed */
+ RZS_UNCOMPRESSED,
+
+ /* Page consists entirely of zeros */
+ RZS_ZERO,
+
+ __NR_RZS_PAGEFLAGS,
+};
+
+/*-- Data structures */
+
+/*
+ * Allocated for each swap slot, indexed by page no.
+ * These table entries must fit exactly in a page.
+ */
+struct table {
+ struct page *page;
+ u16 offset;
+ u8 count; /* object ref count (not yet used) */
+ u8 flags;
+} __attribute__((aligned(4)));
+
+struct ramzswap_stats {
+ /* basic stats */
+ size_t compr_size; /* compressed size of pages stored -
+ * needed to enforce memlimit */
+ /* more stats */
+#if defined(CONFIG_RAMZSWAP_STATS)
+ u64 num_reads; /* failed + successful */
+ u64 num_writes; /* --do-- */
+ u64 failed_reads; /* should NEVER! happen */
+ u64 failed_writes; /* can happen when memory is too low */
+ u64 invalid_io; /* non-swap I/O requests */
+ u64 notify_free; /* no. of swap slot free notifications */
+ u32 pages_zero; /* no. of zero filled pages */
+ u32 pages_stored; /* no. of pages currently stored */
+ u32 good_compress; /* % of pages with compression ratio<=50% */
+ u32 pages_expand; /* % of incompressible pages */
+#endif
+};
+
+struct ramzswap {
+ struct xv_pool *mem_pool;
+ void *compress_workmem;
+ void *compress_buffer;
+ struct table *table;
+ spinlock_t stat64_lock; /* protect 64-bit stats */
+ struct mutex lock; /* protect compression buffers against
+ * concurrent writes */
+ struct request_queue *queue;
+ struct gendisk *disk;
+ int init_done;
+ /*
+ * This is limit on amount of *uncompressed* worth of data
+ * we can hold. When backing swap device is provided, it is
+ * set equal to device size.
+ */
+ size_t disksize; /* bytes */
+
+ struct ramzswap_stats stats;
+};
+
+/*-- */
+
+/* Debugging and Stats */
+#if defined(CONFIG_RAMZSWAP_STATS)
+static void rzs_stat_inc(u32 *v)
+{
+ *v = *v + 1;
+}
+
+static void rzs_stat_dec(u32 *v)
+{
+ *v = *v - 1;
+}
+
+static void rzs_stat64_inc(struct ramzswap *rzs, u64 *v)
+{
+ spin_lock(&rzs->stat64_lock);
+ *v = *v + 1;
+ spin_unlock(&rzs->stat64_lock);
+}
+
+static u64 rzs_stat64_read(struct ramzswap *rzs, u64 *v)
+{
+ u64 val;
+
+ spin_lock(&rzs->stat64_lock);
+ val = *v;
+ spin_unlock(&rzs->stat64_lock);
+
+ return val;
+}
+#else
+#define rzs_stat_inc(v)
+#define rzs_stat_dec(v)
+#define rzs_stat64_inc(r, v)
+#define rzs_stat64_read(r, v)
+#endif /* CONFIG_RAMZSWAP_STATS */
+
+#endif
--- /dev/null
+/*
+ * 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
+ */
+
+#ifndef _RAMZSWAP_IOCTL_H_
+#define _RAMZSWAP_IOCTL_H_
+
+struct ramzswap_ioctl_stats {
+ u64 disksize; /* user specified or equal to backing swap
+ * size (if present) */
+ u64 num_reads; /* failed + successful */
+ u64 num_writes; /* --do-- */
+ u64 failed_reads; /* should NEVER! happen */
+ u64 failed_writes; /* can happen when memory is too low */
+ u64 invalid_io; /* non-swap I/O requests */
+ u64 notify_free; /* no. of swap slot free notifications */
+ u32 pages_zero; /* no. of zero filled pages */
+ u32 good_compress_pct; /* no. of pages with compression ratio<=50% */
+ u32 pages_expand_pct; /* no. of incompressible pages */
+ u32 pages_stored;
+ u32 pages_used;
+ u64 orig_data_size;
+ u64 compr_data_size;
+ u64 mem_used_total;
+} __attribute__ ((packed, aligned(4)));
+
+#define RZSIO_SET_DISKSIZE_KB _IOW('z', 0, size_t)
+#define RZSIO_GET_STATS _IOR('z', 1, struct ramzswap_ioctl_stats)
+#define RZSIO_INIT _IO('z', 2)
+#define RZSIO_RESET _IO('z', 3)
+
+#endif