/*++
/some descriptions of this software.
Copyright ©2014 WonderMediaTechnologies, Inc.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 2 of the License, or(at your option) any
later version.
This program is distributed in the hope that it will be useful,but WITHOUT
ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESSFOR A PARTICULAR PURPOSE. See
the GNU General Public License for more details.You should have received
a copy of the GNU General Public License along with this program. If not,
see .
WonderMediaTechnologies, Inc.
4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
--*/
/*
* Swap block device support for MTDs
* Turns an MTD device into a swap device with block wear leveling
*
* Copyright 漏 2007,2011 Nokia Corporation. All rights reserved.
*
* Authors: Jarkko Lavinen
*
* Based on Richard Purdie's earlier implementation in 2007. Background
* support and lock-less operation written by Adrian Hunter.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MTDSWAP_VERSION "1.0"
#define MTDSWAP_SECTOR_SIZE 4096
#define MTDSWAP_SECTOR_SHIFT 12
#define STATUS_FREE (0xff)
#define STATUS_USED (0x55)
#define MTDSWAP_IO_RETRIES 3
int suspend_counts __nosavedata;
int eba_tbl[1024] __nosavedata;
enum {
MTDSWAP_SCANNED_FREE,
MTDSWAP_SCANNED_USED,
MTDSWAP_SCANNED_BAD,
};
struct mtdswap_oobdata {
unsigned int magic;
unsigned int erase_count;
unsigned int lnum;
unsigned int seq_number;
};
struct mtdswap_eb {
union {
struct rb_node rb;
struct rb_root *root;
} u;
unsigned int erase_count;
unsigned int lnum;
unsigned int pnum;
unsigned int seq_number;
};
struct mtdswap_dev {
struct mtd_blktrans_dev *mbd;
struct mtd_info *mtd; /* mtd device */
struct device *dev;
struct notifier_block pm_notifier;
struct mtdswap_eb *eb_data;
spinlock_t mtdswap_lock;
struct rb_root used;
struct rb_root free;
unsigned int pblocks;
unsigned int badblock;
unsigned int freeblock;
unsigned int usedblock;
unsigned int page_per_block;
unsigned int sector_per_block;
unsigned int mean_count;
unsigned int seq_number;
struct mutex cache_mutex;
unsigned char *cache_data;
unsigned long cache_offset;
unsigned int cache_size;
unsigned char *oob_data;
enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;
};
#define MTDSWAP_MBD_TO_MTDSWAP(dev) ((struct mtdswap_dev *)dev->priv)
unsigned char partitions[32] = "16";
extern char resume_file[64]; /*defined in hibernation.c*/
static char *parts = NULL; /* str: mtd part number defined by resume_file */
static unsigned long part = 0; /* mtd part number defined by resume_file */
static DEFINE_MUTEX(mtdswap_lock);
extern void print_nand_buffer(char *value, unsigned int length);
static void mtdswap_cleanup(struct mtdswap_dev *d);
static int mtdswap_check_resume(struct mtdswap_dev *d);
static int swap_tree_add(struct mtdswap_eb *eb, struct rb_root *root);
static loff_t mtdswap_eb_offset(struct mtdswap_dev *d, struct mtdswap_eb *eb);
void print_mapping_table(struct mtdswap_dev *d)
{
int i;
for (i = 0; i < d->pblocks; i++)
printk("\n After checking, lnum%d pnum%d", i, eba_tbl[i]);
}
static void swaptree_destroy(struct rb_root *root)
{
struct rb_node *rb;
struct mtdswap_eb *e;
rb = root->rb_node;
while (rb) {
if (rb->rb_left)
rb = rb->rb_left;
else if (rb->rb_right)
rb = rb->rb_right;
else {
e = rb_entry(rb, struct mtdswap_eb, u.rb);
rb = rb_parent(rb);
if (rb) {
if (rb->rb_left == &e->u.rb)
rb->rb_left = NULL;
else
rb->rb_right = NULL;
}
/* kfree(e); */
}
}
}
static void mtdswap_cleanup(struct mtdswap_dev *d)
{
swaptree_destroy(&d->used);
swaptree_destroy(&d->free);
vfree(d->eb_data);
vfree(d->cache_data);
vfree(d->oob_data);
}
static unsigned int get_logic_block(struct mtdswap_dev *d, unsigned int pos)
{
return pos / d->mtd->erasesize;
}
static unsigned int get_logic_page(struct mtdswap_dev *d, unsigned int pos)
{
return pos % d->mtd->erasesize;
}
struct mtdswap_eb *find_mtdswap_eb(struct rb_root *root, int diff)
{
struct rb_node *p;
struct mtdswap_eb *e;
e = rb_entry(rb_first(root), struct mtdswap_eb, u.rb);
p = root->rb_node;
while (p) {
struct mtdswap_eb *e1;
e1 = rb_entry(p, struct mtdswap_eb, u.rb);
if (e1->erase_count > diff)
p = p->rb_left;
else {
p = p->rb_right;
e = e1;
}
}
return e;
}
static int find_new_block(struct mtdswap_dev *d, int lnum)
{
/* first we find block from free tree */
int key = 0;
struct mtdswap_eb *eb;
d->seq_number++;
eb = find_mtdswap_eb(&d->free, key);
if (eb == NULL) {
eb = find_mtdswap_eb(&d->used, key);
if (eb == NULL)
return -1;
rb_erase(&eb->u.rb, &d->used);
eb->erase_count++;
eb->lnum = lnum;
eb->seq_number = d->seq_number;
} else {
rb_erase(&eb->u.rb, &d->free);
if (eb->erase_count == 0)
eb->erase_count = d->mean_count;
eb->lnum = lnum;
eb->seq_number = d->seq_number;
}
eba_tbl[lnum] = eb->pnum;
return eb->pnum;
}
static int mtdswap_handle_badblock(struct mtdswap_dev *d, struct mtdswap_eb *eb)
{
int ret;
loff_t offset;
if (!mtd_can_have_bb(d->mtd))
return 1;
offset = mtdswap_eb_offset(d, eb);
dev_warn(d->dev, "Marking bad block at %08llx\n", offset);
ret = mtd_block_markbad(d->mtd, offset);
if (ret) {
dev_warn(d->dev, "Mark block bad failed for block at %08llx "
"error %d\n", offset, ret);
return ret;
}
return 1;
}
static int swap_erase(struct mtdswap_dev *d, struct erase_info *erase)
{
struct mtd_info *mtd = d->mtd;
struct mtdswap_eb *eb;
unsigned long pos = erase->addr;
int lnum = get_logic_block(d, pos);
int page = get_logic_page(d, pos);
int pnum, ret = 0, retries = 0;
if (eba_tbl[lnum] != -1) {
eb = d->eb_data + eba_tbl[lnum];
spin_lock(&d->mtdswap_lock);
swap_tree_add(eb, &d->used);
spin_unlock(&d->mtdswap_lock);
}
RETRY:
spin_lock(&d->mtdswap_lock);
pnum = find_new_block(d, lnum);
/*printk("\n lnum %d -> %d", lnum, pnum); */
spin_unlock(&d->mtdswap_lock);
if (pnum == -1)
return -EIO;
eb = d->eb_data + pnum;
erase->addr = pnum * mtd->erasesize + page;
ret = mtd_erase(mtd, erase);
if (ret) {
mtdswap_handle_badblock(d, eb);
retries++;
if (retries > MTDSWAP_IO_RETRIES)
return -EIO;
goto RETRY;
}
return 0;
}
static int mtdswap_write_marker(struct mtdswap_dev *d, struct mtdswap_eb *eb,
loff_t offset, size_t len, unsigned char *buf)
{
struct mtdswap_oobdata *data;
struct mtd_info *mtd = d->mtd;
int ret;
struct mtd_oob_ops ops;
data = (struct mtdswap_oobdata *)d->oob_data;
ops.len = ((len >= mtd->writesize) ? mtd->writesize : len);
ops.ooblen = 16;
ops.oobbuf = d->oob_data;
ops.ooboffs = 0;
ops.datbuf = buf;
ops.mode = MTD_OPS_AUTO_OOB;
data->magic = cpu_to_le32(STATUS_USED);
data->erase_count = cpu_to_le32(eb->erase_count);
data->lnum = cpu_to_le32(eb->lnum);
data->seq_number = cpu_to_le32(eb->seq_number);
ret = mtd_write_oob(mtd, offset, &ops);
return ret;
}
static int swap_write(struct mtdswap_dev *d, unsigned long pos, size_t len,
size_t *retlen, unsigned char *buf)
{
struct mtd_info *mtd = d->mtd;
int lnum = get_logic_block(d, pos);
int page = get_logic_page(d, pos);
int pnum = eba_tbl[lnum];
unsigned long addr = pnum * mtd->erasesize + page;
struct mtdswap_eb *eb = d->eb_data + pnum;
int ret;
*retlen = len;
/* First, write datbuf and oobbuf */
ret = mtdswap_write_marker(d, eb, addr, len, buf);
if (ret) {
mtdswap_handle_badblock(d, eb);
return ret;
}
/* Second, just write databuf */
len -= mtd->writesize;
if (len <= 0)
return 0;
ret =
mtd_write(mtd, addr + mtd->writesize, len, retlen,
buf + mtd->writesize);
/*printk("\nwrite data to %d, %s", pnum, current->comm); */
if (ret) {
mtdswap_handle_badblock(d, eb);
return ret;
}
*retlen += mtd->writesize;
return ret;
}
static int swap_read(struct mtdswap_dev *d, unsigned long pos, size_t len,
size_t *retlen, unsigned char *buf)
{
struct mtd_info *mtd = d->mtd;
int lnum = get_logic_block(d, pos);
int page = get_logic_page(d, pos);
int pnum = eba_tbl[lnum];
unsigned long addr = pnum * mtd->erasesize + page;
/*
printk("\nread data from pos 0x%lx, lnum %d, pnum%d page%d",
pos, lnum, pnum, page);
*/
if (pnum == -1) {
*retlen = len;
return 0;
}
return mtd_read(mtd, addr, len, retlen, buf);
}
static int swap_read_oob(struct mtdswap_dev *d, loff_t from,
struct mtd_oob_ops *ops)
{
int ret = mtd_read_oob(d->mtd, from, ops);
return ret;
}
static void erase_callback(struct erase_info *done)
{
wait_queue_head_t *wait_q = (wait_queue_head_t *) done->priv;
wake_up(wait_q);
}
static int erase_write(struct mtdswap_dev *d, unsigned long pos,
int len, unsigned char *buf)
{
struct erase_info erase;
struct mtd_info *mtd = d->mtd;
DECLARE_WAITQUEUE(wait, current);
wait_queue_head_t wait_q;
size_t retlen;
int ret, retries = 0;
/*
* First, let's erase the flash block.
*/
#if 0
if (pos == 0x0)
printk("\n Update Swap Header!");
#endif
RETRY:
init_waitqueue_head(&wait_q);
erase.mtd = mtd;
erase.callback = erase_callback;
erase.len = len;
erase.addr = pos;
erase.priv = (u_long) & wait_q;
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&wait_q, &wait);
ret = swap_erase(d, &erase);
if (ret) {
set_current_state(TASK_RUNNING);
remove_wait_queue(&wait_q, &wait);
return ret;
}
schedule(); /* Wait for erase to finish. */
remove_wait_queue(&wait_q, &wait);
/*
* Next, write the data to flash.
*/
ret = swap_write(d, pos, len, &retlen, buf);
if (ret) {
retries++;
if (retries > MTDSWAP_IO_RETRIES)
return -EIO;
goto RETRY;
}
if (retlen != len)
return -EIO;
return 0;
}
static int write_cached_data(struct mtdswap_dev *d)
{
int ret;
if (d->cache_state != STATE_DIRTY)
return 0;
ret = erase_write(d, d->cache_offset, d->cache_size, d->cache_data);
if (ret)
return ret;
d->cache_state = STATE_EMPTY;
return 0;
}
static int do_cached_write(struct mtdswap_dev *d, unsigned long pos,
unsigned int len, unsigned char *buf)
{
unsigned int sect_size = d->cache_size;
size_t retlen;
int ret;
/* print_nand_buffer(buf, len); */
while (len > 0) {
unsigned long sect_start = (pos / sect_size) * sect_size;
unsigned int offset = pos - sect_start;
unsigned int size = sect_size - offset;
if (size > len)
size = len;
if (size == sect_size) {
ret = erase_write(d, pos, size, buf);
if (ret)
return ret;
} else {
if (d->cache_state == STATE_DIRTY &&
d->cache_offset != sect_start) {
mutex_lock(&d->cache_mutex);
ret = write_cached_data(d);
mutex_unlock(&d->cache_mutex);
if (ret)
return ret;
}
if (d->cache_state == STATE_EMPTY ||
d->cache_offset != sect_start) {
d->cache_state = STATE_EMPTY;
ret = swap_read(d, sect_start, sect_size,
&retlen, d->cache_data);
if (ret)
return ret;
if (retlen != sect_size)
return -EIO;
d->cache_offset = sect_start;
d->cache_state = STATE_CLEAN;
}
memcpy(d->cache_data + offset, buf, size);
d->cache_state = STATE_DIRTY;
}
buf += size;
pos += size;
len -= size;
}
return 0;
}
static int do_cached_read(struct mtdswap_dev *d, unsigned long pos,
int len, char *buf)
{
unsigned int sect_size = d->cache_size;
size_t retlen;
int ret;
/* printk("\n Read data from pos 0x%lx, len 0x%x", pos, len); */
mutex_lock(&d->cache_mutex);
while (len > 0) {
unsigned long sect_start = (pos / sect_size) * sect_size;
unsigned int offset = pos - sect_start;
unsigned int size = sect_size - offset;
if (size > len)
size = len;
if (d->cache_state != STATE_EMPTY &&
d->cache_offset == sect_start) {
memcpy(buf, d->cache_data + offset, size);
} else {
ret = swap_read(d, pos, size, &retlen, buf);
if (ret)
return ret;
if (retlen != size)
return -EIO;
}
/* print_nand_buffer(buf, len); */
buf += size;
pos += size;
len -= size;
}
mutex_unlock(&d->cache_mutex);
return 0;
}
static int mtdswap_flush(struct mtd_blktrans_dev *dev)
{
struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev);
mutex_lock(&d->cache_mutex);
write_cached_data(d);
mutex_unlock(&d->cache_mutex);
mtd_sync(d->mtd);
return 0;
}
static int mtdswap_readsect(struct mtd_blktrans_dev *dev, unsigned long block,
char *buf)
{
struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev);
if (likely(dev->mtd->writesize >= MTDSWAP_SECTOR_SIZE))
return do_cached_read(d, block << MTDSWAP_SECTOR_SHIFT,
MTDSWAP_SECTOR_SIZE, buf);
return do_cached_read(d, block << 9, 512, buf);
}
static int mtdswap_writesect(struct mtd_blktrans_dev *dev, unsigned long block,
char *buf)
{
struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev);
if (likely(dev->mtd->writesize >= MTDSWAP_SECTOR_SIZE))
return do_cached_write(d, block << MTDSWAP_SECTOR_SHIFT,
MTDSWAP_SECTOR_SIZE, buf);
return do_cached_write(d, block << 9, 512, buf);
}
static void mtdswap_remove_dev(struct mtd_blktrans_dev *dev)
{
struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev);
del_mtd_blktrans_dev(dev);
mtdswap_cleanup(d);
kfree(d);
}
static loff_t mtdswap_eb_offset(struct mtdswap_dev *d, struct mtdswap_eb *eb)
{
return (loff_t) (eb - d->eb_data) * d->mtd->erasesize;
}
static int mtdswap_read_markers(struct mtdswap_dev *d, struct mtdswap_eb *eb)
{
struct mtdswap_oobdata *data;
int ret;
loff_t offset;
struct mtd_oob_ops ops;
offset = mtdswap_eb_offset(d, eb);
if (mtd_can_have_bb(d->mtd) && mtd_block_isbad(d->mtd, offset)) {
d->badblock++;
return MTDSWAP_SCANNED_BAD;
}
ops.ooblen = 16;
ops.oobbuf = d->oob_data;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.mode = MTD_OPS_AUTO_OOB;
ret = swap_read_oob(d, offset, &ops);
data = (struct mtdswap_oobdata *)d->oob_data;
if (le32_to_cpu(data->magic) == STATUS_USED) {
eb->erase_count = le32_to_cpu(data->erase_count);
eb->lnum = le32_to_cpu(data->lnum);
eb->seq_number = le32_to_cpu(data->seq_number);
d->usedblock++;
d->mean_count += eb->erase_count;
if (eb->seq_number > d->seq_number)
d->seq_number = eb->seq_number;
ret = MTDSWAP_SCANNED_USED;
} else {
eb->erase_count = 0;
d->freeblock++;
ret = MTDSWAP_SCANNED_FREE;
}
eb->pnum = (unsigned int)(eb - d->eb_data);
return ret;
}
static int swap_tree_add(struct mtdswap_eb *eb, struct rb_root *root)
{
struct rb_node **p, *parent = NULL;
p = &root->rb_node;
while (*p) {
struct mtdswap_eb *eb1;
parent = *p;
eb1 = rb_entry(parent, struct mtdswap_eb, u.rb);
if (eb->erase_count < eb1->erase_count)
p = &(*p)->rb_left;
else if (eb->erase_count > eb1->erase_count)
p = &(*p)->rb_right;
else {
if (eb->pnum == eb1->pnum)
return 0;
if (eb->pnum < eb1->pnum)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
}
rb_link_node(&eb->u.rb, parent, p);
rb_insert_color(&eb->u.rb, root);
return 0;
}
static int build_mapping_table(struct mtdswap_dev *d, struct mtdswap_eb *eb)
{
int pnum;
struct mtdswap_eb *eb1;
pnum = eba_tbl[eb->lnum];
if (pnum >= 0) {
eb1 = d->eb_data + pnum;
if (eb1->seq_number > eb->seq_number)
return 0;
}
eba_tbl[eb->lnum] = eb->pnum;
return 0;
}
static int mtdswap_check_counts(struct mtdswap_dev *d)
{
return (d->pblocks - d->usedblock - d->freeblock - d->badblock) ? 1 : 0;
}
static int mtdswap_scan_eblks(struct mtdswap_dev *d, unsigned int need_build)
{
int status, i;
struct mtdswap_eb *eb;
for (i = 0; i < d->pblocks; i++) {
eb = d->eb_data + i;
eb->pnum = i;
status = mtdswap_read_markers(d, eb);
if (status == MTDSWAP_SCANNED_BAD)
continue;
switch (status) {
case MTDSWAP_SCANNED_FREE:
spin_lock(&d->mtdswap_lock);
swap_tree_add(eb, &d->free);
spin_unlock(&d->mtdswap_lock);
break;
case MTDSWAP_SCANNED_USED:
spin_lock(&d->mtdswap_lock);
swap_tree_add(eb, &d->used);
spin_unlock(&d->mtdswap_lock);
if(need_build)
build_mapping_table(d, eb);
break;
}
}
if (mtdswap_check_counts(d))
printk(KERN_CRIT "\n NOTICE: MTDSWAP counts are illegal");
return 0;
}
#if 0
static void test_swap(struct mtdswap_dev *d)
{
unsigned long start_sector = 0x0;
unsigned long sector_count = 0;
unsigned long rand_seed = 544;
unsigned char write_data = 0;
unsigned int i;
int ret;
for (i = 0; i < 10000; i++) {
/* seed the randome: no seed to freeze the test case */
srandom32(random32() + i + rand_seed);
/* start_sector = (unsigned long)(random32()%(d->sector_per_block * 64)) & (~(32-1));
rand_seed = (unsigned long)(random32()%(d->sector_per_block * 64-start_sector));
*/
write_data = (unsigned char)(random32() % ((unsigned char)-1));
sector_count = 1;
/* set data */
memset(sector_buffer, (unsigned char)write_data, 2097152);
/* write */
/* ret = ONFM_Write(c, start_sector, sector_count, sector_buffer); */
ret = do_cached_write(d, start_sector, 2097152, sector_buffer);
/* ret = erase_write(d, start_sector, 512, sector_buffer); */
if (ret == 0) {
/* read and check */
ret =
do_cached_read(d, start_sector, 2097152,
read_sector_buffer);
if (ret == 0) {
ret =
memcmp(sector_buffer, read_sector_buffer,
2097152);
}
}
/* print */
if (ret != 0) {
printk
("\n%d:*FAIL* start address: %d, sector count: %d, data: %d",
i, start_sector, sector_count, write_data);
break;
} else {
printk
("\n%d-PASS. start address: %d, sector count: %d, data: %d.",
i, start_sector, sector_count, write_data);
start_sector += 0x200;
}
}
}
#endif
static int mtdswap_check_resume(struct mtdswap_dev *d)
{
struct mtd_info *mtd = d->mtd;
struct mtdswap_eb *eb;
spin_lock(&d->mtdswap_lock);
swaptree_destroy(&d->used);
swaptree_destroy(&d->free);
spin_unlock(&d->mtdswap_lock);
d->mean_count = 1;
d->used = d->free = RB_ROOT;
d->badblock = d->freeblock = d->usedblock = 0;
memset(d->eb_data, 0x00, sizeof(struct mtdswap_eb) * d->pblocks);
mutex_lock(&d->cache_mutex);
d->cache_size = mtd->erasesize;
d->cache_state = STATE_EMPTY;
d->cache_offset = -1;
memset(d->cache_data, 0xFF, mtd->erasesize);
mutex_unlock(&d->cache_mutex);
memset(d->oob_data, 0xFF, mtd->oobsize);
mtdswap_scan_eblks(d, 0);
eb = d->eb_data + eba_tbl[0];
spin_lock(&d->mtdswap_lock);
rb_erase(&eb->u.rb, &d->used);
spin_unlock(&d->mtdswap_lock);
#if 0
for (i = 0; i < d->pblocks; i++) {
if (eba_tbl[i] != -1) {
eb = d->eb_data + eba_tbl[i];
printk("\n Remove %d from used tree", eb->pnum);
rb_erase(&eb->u.rb, &d->used);
}
}
#endif
if (d->usedblock)
d->mean_count = d->mean_count / d->usedblock;
return 0;
}
static int mtdswap_check_suspend(struct mtdswap_dev *d)
{
struct mtd_info *mtd = d->mtd;
struct mtdswap_eb *eb;
int i;
spin_lock(&d->mtdswap_lock);
swaptree_destroy(&d->used);
swaptree_destroy(&d->free);
spin_unlock(&d->mtdswap_lock);
d->mean_count = 1;
d->used = d->free = RB_ROOT;
d->badblock = d->freeblock = d->usedblock = 0;
memset(d->eb_data, 0x00, sizeof(struct mtdswap_eb) * d->pblocks);
mutex_lock(&d->cache_mutex);
d->cache_size = mtd->erasesize;
d->cache_state = STATE_EMPTY;
d->cache_offset = -1;
memset(d->cache_data, 0xFF, mtd->erasesize);
mutex_unlock(&d->cache_mutex);
memset(d->oob_data, 0xFF, mtd->oobsize);
if(!suspend_counts) {
for (i = 1; i < d->pblocks; i++)
eba_tbl[i] = -1;
}
mtdswap_scan_eblks(d, 0);
eb = d->eb_data + eba_tbl[0];
spin_lock(&d->mtdswap_lock);
rb_erase(&eb->u.rb, &d->used);
spin_unlock(&d->mtdswap_lock);
suspend_counts = 1;
#if 0
for (i = 0; i < d->pblocks; i++) {
if (eba_tbl[i] != -1) {
eb = d->eb_data + eba_tbl[i];
rb_erase(&eb->u.rb, &d->used);
}
}
#endif
if (d->usedblock)
d->mean_count = d->mean_count / d->usedblock;
return 0;
}
static int mtdswap_resume(struct mtdswap_dev *d)
{
mtdswap_check_resume(d);
return 0;
}
static int mtdswap_suspend(struct mtdswap_dev *d)
{
mtdswap_check_suspend(d);
return 0;
}
static int swap_power_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct mtdswap_dev *d =
container_of(this, struct mtdswap_dev, pm_notifier);
switch (event) {
case PM_POST_RESTORE: /* in case hibernation restore fail */
case PM_POST_HIBERNATION: /* normal case for hibernation finished */
mtdswap_resume(d);
break;
case PM_HIBERNATION_PREPARE:
mtdswap_suspend(d);
break;
case PM_HIBERNATION_FINISH:
mutex_lock(&d->cache_mutex);
write_cached_data(d);
mutex_unlock(&d->cache_mutex);
break;
default:
break;
}
return NOTIFY_DONE;
}
static int mtdswap_init(struct mtdswap_dev *d, unsigned int eblocks)
{
struct mtd_info *mtd = d->mbd->mtd;
struct mtdswap_eb *eb;
int i;
d->mtd = mtd;
d->pblocks = eblocks;
d->pm_notifier.notifier_call = swap_power_event;
register_pm_notifier(&d->pm_notifier);
d->page_per_block = mtd->erasesize / mtd->writesize;
d->sector_per_block = mtd->erasesize >> MTDSWAP_SECTOR_SHIFT;
d->mean_count = 1;
d->used = d->free = RB_ROOT;
spin_lock_init(&d->mtdswap_lock);
mutex_init(&d->cache_mutex);
d->badblock = d->freeblock = d->usedblock = 0;
d->cache_data = vmalloc(mtd->erasesize);
d->cache_size = mtd->erasesize;
d->cache_state = STATE_EMPTY;
d->cache_offset = -1;
d->oob_data = vmalloc(mtd->oobsize);
d->eb_data = vmalloc(sizeof(struct mtdswap_eb) * d->pblocks);
memset(d->eb_data, 0x00, sizeof(struct mtdswap_eb) * d->pblocks);
memset(d->cache_data, 0xFF, mtd->erasesize);
memset(d->oob_data, 0xFF, mtd->oobsize);
for (i = 0; i < d->pblocks; i++)
eba_tbl[i] = -1;
mtdswap_scan_eblks(d, 1);
for (i = 0; i < d->pblocks; i++) {
if (eba_tbl[i] != -1) {
eb = d->eb_data + eba_tbl[i];
rb_erase(&eb->u.rb, &d->used);
}
}
#if 0
for (i = 0; i < d->pblocks; i++)
printk("\n lnum%d pnum%d", i, eba_tbl[i]);
#endif
if (d->usedblock)
d->mean_count = d->mean_count / d->usedblock;
/* test_swap(d); */
return 0;
}
static int mtdswap_find_mtd(unsigned char *target, unsigned char *source)
{
/*extract partition number from string */
unsigned char *temp;
unsigned int slen = strlen(source);
unsigned int tlen=0;
temp = strstr(target, source);
if (temp) {
tlen = strlen(temp);
strncpy(partitions, temp + slen, tlen-slen+1);
/*find mtd = true*/
return 1;
}
/*find mtd = false*/
return 0;
}
static void mtdswap_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
struct mtdswap_dev *d;
struct mtd_blktrans_dev *mbd_dev;
struct nand_ecclayout *info;
unsigned long use_size;
int eblocks;
if (memcmp(mtd->name, "swap", sizeof("swap"))!=0)
return;
if (mtd->index != part){
printk(KERN_WARNING"\n Find swap partition mtdswap%d != mtdswap%lu\n", mtd->index, part);
/*replace original resume_file with what we actaully find.*/
memset(resume_file, 0, sizeof(resume_file));
strncat(resume_file, "/dev/mtdswap", sizeof("/dev/mtdswap"));
snprintf(partitions, sizeof(partitions), "%d", mtd->index);
strncat(resume_file, partitions, sizeof(partitions));
printk(KERN_WARNING"Replace resume_file As : %s\n", resume_file);
}
printk(KERN_INFO "Enabling MTD swap on device %d, size %lldMB, ",
mtd->index, mtd->size / 1024 / 1024);
info = mtd->ecclayout;
use_size = mtd->size;
eblocks = mtd_div_by_eb(use_size, mtd);
d = kzalloc(sizeof(struct mtdswap_dev), GFP_KERNEL);
if (!d)
return;
mbd_dev = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
if (!mbd_dev) {
kfree(d);
return;
}
d->mbd = mbd_dev;
mbd_dev->priv = d;
mbd_dev->mtd = mtd;
mbd_dev->devnum = mtd->index;
mbd_dev->size = use_size >> 9;
mbd_dev->tr = tr;
if (!(mtd->flags & MTD_WRITEABLE))
mbd_dev->readonly = 1;
if (mtdswap_init(d, eblocks) < 0)
goto init_failed;
if (add_mtd_blktrans_dev(mbd_dev) < 0)
goto cleanup;
d->dev = disk_to_dev(mbd_dev->disk);
return;
cleanup:
mtdswap_cleanup(d);
init_failed:
kfree(mbd_dev);
kfree(d);
}
static int mtdswap_open(struct mtd_blktrans_dev *dev)
{
return 0;
}
static int mtdswap_release(struct mtd_blktrans_dev *dev)
{
struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev);
mutex_lock(&d->cache_mutex);
write_cached_data(d);
mutex_unlock(&d->cache_mutex);
return 0;
}
static struct mtd_blktrans_ops mtdswap_ops = {
.name = "mtdswap",
.major = 0,
.part_bits = 0,
.blksize = 512,
.open = mtdswap_open,
.flush = mtdswap_flush,
.release = mtdswap_release,
.readsect = mtdswap_readsect,
.writesect = mtdswap_writesect,
.add_mtd = mtdswap_add_mtd,
.remove_dev = mtdswap_remove_dev,
.owner = THIS_MODULE,
};
static int __init mtdswap_modinit(void)
{
/* find if resume_file name contains "mtdswap" */
int ret = mtdswap_find_mtd(resume_file, "mtdswap");
if (!ret){
printk(KERN_WARNING"\n[mtdswap] Resume Partition Is Not mtdswap !!!\n");
return 0;
}
parts = &partitions[0];
printk(KERN_WARNING"[mtdswap] resume_file:%s, parts=%s\n", resume_file, parts);
if(kstrtoul(parts, 0, &part) < 0){
printk(KERN_WARNING"[mtdswap] Invalid MTDSWAP Partition Number!!!\n");
}
return register_mtd_blktrans(&mtdswap_ops);
}
static void __exit mtdswap_modexit(void)
{
deregister_mtd_blktrans(&mtdswap_ops);
}
module_init(mtdswap_modinit);
module_exit(mtdswap_modexit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Johnny Liu ");
MODULE_DESCRIPTION("Block device access to an MTD suitable for using as "
"swap space");