/*
 * arch/arm/common/warmboot.c
 *
 * image creation driver I/F
 *
 * Copyright 2005,2006 Sony Corporation
 * 
 * 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 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 *
 */

#include <linux/suspend.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sysfs.h>
#include <linux/pm.h>
#include <linux/vmalloc.h>
#include <linux/lz77.h>
#include <linux/rle.h>

#include <asm/page.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/cacheflush.h>
#include <asm/mach/warmboot.h>
#include <asm/io.h>

#define DEFAULT_SECTOR_SIZE 512
#define RU_SIZE(len, alignment) (((len) + (alignment) -1)&~((alignment)-1))
#define RU_SECTOR_SIZE(len) RU_SIZE(len, wb_default_device->phys_sector_size)

#define wb_msg(fmt,arg...)                                   \
        do{                                                    \
	        if(!(wb_default_device->flag&WBI_SILENT_MODE)) \
                        printk(fmt, ##arg);                    \
	}while(0);                                             \
			
extern struct kset power_subsys;

#define WBI_MODE(state, mode) ((state) == PM_SUSPEND_DISK && (mode))

#define wb_attr(_name) \
static struct subsys_attribute _name##_attr = {	\
	.attr	= {				\
		.name = __stringify(_name),	\
		.mode = 0644,			\
	},					\
	.show	= _name##_show,			\
	.store	= _name##_store,		\
}

/*
  warm boot device list
 */
static LIST_HEAD(wb_device_list);
static DECLARE_MUTEX(wb_device_sem);

/*
  warm boot memory region list
 */
static LIST_HEAD(wb_mem_list);
static DECLARE_MUTEX(wb_mem_sem);

struct wb_user_mem_reg {
	unsigned long act;
	unsigned long phys;
	unsigned long virt;
	unsigned long size;
};

struct wb_mem_reg {
	struct list_head list;
	unsigned long io_virt;

	struct wb_user_mem_reg user_data;
};

static struct wb_device *wb_default_device;
static unsigned char *sendbuf;
static unsigned long buf_top;
static u32 cur_sector;

#define C_BUFFSIZE (PAGE_SIZE*3)
static unsigned char *c_buff;/*buffer for deflation*/

static int send_data(void *b, unsigned long len, unsigned long *cksum);
static int flush_sendbuf(void);

enum {
	IMAGE_MODE_NONE = 0,
	IMAGE_MODE_MIN,
	IMAGE_MODE_MAX,
	IMAGE_MODE_ADP,
	IMAGE_MODE_NUM
};

static unsigned image_mode=IMAGE_MODE_NONE;
static suspend_state_t pm_state=PM_SUSPEND_MEM;

typedef int (*callback_t)(unsigned long phys, unsigned long virt, unsigned long flag, unsigned long data);

static ssize_t image_mode_show(struct kset * subsys, char * buf)
{
	return sprintf(buf, "%u\n", image_mode);
}

static ssize_t image_mode_store(struct kset * subsys, const char * buf, size_t n)
{
	unsigned char char_value = *(unsigned char *)buf;
	int value;

	if(n != 1)
		return -EINVAL;

	if(char_value < '0')
		return 0;

	value = char_value - '0';
	if(value<IMAGE_MODE_NUM)
		image_mode = value;
	else
		return -EINVAL;

	return n;
}

static ssize_t image_dev_show(struct kset * subsys, char * buf)
{
	struct wb_device *wb_device;
	char *src = buf;

	if(down_interruptible(&wb_device_sem))
		return -EINTR;
	
	list_for_each_entry(wb_device, &wb_device_list, list) {
		src += sprintf(src, "%s%s\n",
			       wb_device==wb_default_device?"*":" ",
			       wb_device->name);
	}

	up(&wb_device_sem);
	return (int)(src-buf);
}

static ssize_t image_dev_store(struct kset * subsys, const char * buf, size_t n)
{
	char *endp;
	int value;
	struct wb_device *wb_device;

	if(n >= PAGE_SIZE)
		return -EINVAL;

	value = simple_strtoul(buf, &endp, 0);
	if(*endp || !value)
		return -EINVAL;
	
	if(down_interruptible(&wb_device_sem))
		return -EINTR;
	
	list_for_each_entry(wb_device, &wb_device_list, list) {
		value--;
		if(value == 0){
			wb_default_device = wb_device;
			break;
		}
	}

	up(&wb_device_sem);
	return value?-EINVAL:n;
}

#define RECORD_LEN (sizeof("0xXXXXXXXX,0xXXXXXXXX,0xXXXXXXXXK\n")-1)
#define USER_DATA_LEN sizeof(struct wb_user_mem_reg)
#define USER_DATA_MASK (~(USER_DATA_LEN-1))

static ssize_t image_mem_show(struct kset * subsys, char * buf){
	struct wb_mem_reg *mem_reg;
	char *src = buf;
	
	if(down_interruptible(&wb_mem_sem))
		return -EINTR;
	
	list_for_each_entry(mem_reg, &wb_mem_list, list) {
		src += sprintf(src, "%8lX,%8lX,%8luK\n",
				    mem_reg->user_data.phys,
				    mem_reg->user_data.virt,
				    mem_reg->user_data.size >> 10);

	}

	up(&wb_mem_sem);

	return (src - buf);
}

static ssize_t image_mem_store(struct kset * subsys, const char * buf, size_t n){
	struct wb_mem_reg *mem_reg = NULL;
	struct wb_user_mem_reg *user_data = NULL;
	unsigned long count = n;
	ssize_t ret = n;

	if(count & ~USER_DATA_MASK)
		return -EINVAL;

	if(down_interruptible(&wb_mem_sem))
		return -EINTR;
	
	while(count){
		count -= USER_DATA_LEN;
		
		if(!(mem_reg = kmalloc(sizeof(struct wb_mem_reg), GFP_KERNEL))){
			ret = -ENOMEM;
			goto error;
		}

		memset(mem_reg, 0, sizeof(*mem_reg));
		user_data = &mem_reg->user_data;
		memcpy(user_data, buf, USER_DATA_LEN);
		buf += USER_DATA_LEN;

		/*
		  valid value:
		  1, phys && size
		  2, phys, size, virt page alignment
		  3, act 0 || 1
		*/
		if(user_data->act == 0) {//clear all memory region
			printk(KERN_INFO "clear all memory region\n");
		
			kfree(mem_reg);
		
			while(!list_empty(&wb_mem_list)){
				mem_reg = list_entry(wb_mem_list.next, struct wb_mem_reg, list);
			
				list_del(&mem_reg->list);
				kfree(mem_reg);
			}

			continue;
		}

		if((!user_data->phys)
		   || (user_data->phys & ~PAGE_MASK)
		   || (!user_data->size)
		   || (user_data->size & ~PAGE_MASK)
		   || (user_data->virt & ~PAGE_MASK)
		   || (user_data->act != 1))
		{
			kfree(mem_reg);
			ret = -EINVAL;
			break;
		}

		printk(KERN_INFO "add memory region: %8lX(@%08lX),%8lX(%ldK)\n",
		       user_data->phys,
		       user_data->virt,
		       user_data->size,
		       user_data->size >> 10);
		
		list_add_tail(&mem_reg->list, &wb_mem_list);
	}

 error:	
	up(&wb_mem_sem);
	return ret;
}

wb_attr(image_mode);
wb_attr(image_dev);
wb_attr(image_mem);

static int __init create_warm_boot_proc(void) 
{
	int error = 0;

	error = subsys_create_file(&power_subsys, &image_mode_attr);
	if (error)
		goto out;
	error = subsys_create_file(&power_subsys, &image_dev_attr);
	if (error){
		goto out;
	}
	error = subsys_create_file(&power_subsys, &image_mem_attr);
	if (error){
		goto out;
	}
 out:
	return error;
}

late_initcall(create_warm_boot_proc);

static void iounmap_ext_pages(void)
{
	struct wb_mem_reg *mem_reg;
	
	down(&wb_mem_sem);
	
	list_for_each_entry(mem_reg, &wb_mem_list, list) {
		if(mem_reg->user_data.virt)
			continue;

		if(mem_reg->io_virt){
			iounmap((void __iomem *)mem_reg->io_virt);
			mem_reg->io_virt = 0;
		}
	}
	
	up(&wb_mem_sem);
}

static int ioremap_ext_pages(void)
{
	struct wb_mem_reg *mem_reg;
	const char __user *buf;
	unsigned long test, i;
	int error = 0;
	
	down(&wb_mem_sem);

	list_for_each_entry(mem_reg, &wb_mem_list, list) {
		if(mem_reg->user_data.virt){
			mem_reg->io_virt = mem_reg->user_data.virt;

			for(i = 0, buf = (const char __user *)(mem_reg->io_virt);
			    i < mem_reg->user_data.size;
			    i += PAGE_SIZE, buf += PAGE_SIZE)
			{
				if(copy_from_user(&test, buf, sizeof(test))){
					printk(KERN_INFO "cannot access address: %p(%08lx,%08lx,%08lx)\n",
					       buf,
					       mem_reg->user_data.phys,
					       mem_reg->io_virt,
					       mem_reg->user_data.size);
					break;
				}
			}

			if(i == mem_reg->user_data.size>>PAGE_SHIFT){
				printk(KERN_INFO "using user provided virt address: %8lX\n",
				       mem_reg->io_virt);
				continue;
			}

			mem_reg->user_data.virt = 0;
		}

		mem_reg->io_virt = (unsigned long) ioremap(mem_reg->user_data.phys,
							mem_reg->user_data.size);

		if(!mem_reg->io_virt){
			printk(KERN_INFO "ioremap error: %8lX,%8luK",
			       mem_reg->user_data.phys,
			       mem_reg->user_data.size >> 10);

			error = -ENOMEM;

			break;
		}
	}
	
	up(&wb_mem_sem);

	return error;
}

#ifdef CONFIG_DEBUG_LL
extern void printascii(char *);
static int wb_printk(const char *fmt, ...)
{
	va_list args;
	int len;
	static char wb_printk_buf[128];

	if(wb_default_device->flag&WBI_SILENT_MODE)
		return 0;
	
	va_start(args, fmt);

	len = vscnprintf(wb_printk_buf, sizeof(wb_printk_buf)-1, fmt, args);
	wb_printk_buf[len] = 0x0;
	printascii(wb_printk_buf);
	
	va_end(args);
	
	return len;
}
#else
static int wb_printk(const char *fmt, ...)
{
	return 0;
}
#endif

static inline unsigned long default_flags(void)
{
	unsigned long flag=0;
#ifdef CONFIG_WBI_COMPRESS
	flag |= WBI_COMPRESS;
#endif 
#ifdef CONFIG_WBI_NOT_KERNEL_RO
	flag |= WBI_NOT_KERNEL_RO;
#endif 
#ifdef CONFIG_WBI_CKSUM
	flag |= WBI_CKSUM;
#endif
#ifdef CONFIG_WBI_LZ77_COMPRESS
	flag |= WBI_LZ77_COMPRESS;
#endif 
	return flag;
}

enum {
	START=0,
	DOING,
	END,
};

static int count_ext_pages(callback_t callback, unsigned long data)
{
	struct wb_mem_reg *mem_reg;
	int phys, off;
	int ret;
	
	list_for_each_entry(mem_reg, &wb_mem_list, list) {
		for(phys = mem_reg->user_data.phys, off = 0;
		    off < mem_reg->user_data.size;
		    off += PAGE_SIZE, phys += PAGE_SIZE)
		{
			if((ret=callback(phys, mem_reg->io_virt + off, DOING, data)))
				return ret;
		}
	}
	
	return 0;
}

static void free_some_memory(unsigned mode)
{
	if(mode!=IMAGE_MODE_MIN) return;
	printk("Freeing memory: ");
	while (shrink_all_memory(10000))
		printk(".");
	printk("|\n");
}

static int is_head_of_free_region(struct page *page)
{
        struct zone *zone = page_zone(page);
        /*unsigned long flags;*/
	int order, type;
	struct list_head *curr;

	/*
	  we are in irq-off state, so...
	*/
	/*spin_lock_irqsave(&zone->lock, flags);*/
	for (order = MAX_ORDER - 1; order >= 0; --order) {
		for (type = 0; type < MIGRATE_TYPES; type++) {
			list_for_each(curr, &zone->free_area[order].free_list[type]) {
				if (page == list_entry(curr, struct page, lru)) {
					/*spin_unlock_irqrestore(&zone->lock, flags);*/
					return 1 << order;
				}
			}
		}
	}
	/*spin_unlock_irqrestore(&zone->lock, flags);*/
        return 0;
}

extern void _kern_text_start,_kern_text_end;
static int is_kernel_ro_pfn(unsigned long pfn)
{
	unsigned long kn_pfn_start = PAGE_ALIGN(__pa(&_kern_text_start))>>PAGE_SHIFT;
	unsigned long kn_pfn_end = __pa(&_kern_text_end)>>PAGE_SHIFT;
	
	if(default_flags() & WBI_NOT_KERNEL_RO)
		return (pfn>=kn_pfn_start && pfn<kn_pfn_end);
	else
		return 0;
}

static int saveable(struct zone * zone, unsigned long * zone_pfn)
{
	unsigned long pfn = *zone_pfn + zone->zone_start_pfn;
	unsigned long chunk_size;
	struct page * page;
	
	if (!pfn_valid(pfn))
		return 0;

	page = pfn_to_page(pfn);

	if(is_kernel_ro_pfn(pfn)){
		return 0;
	}
	
	if(PageReserved(page)
	   && is_ext_reserved_area(virt_to_phys(page_address(page)))){
		return 0;
	}

	if ((chunk_size = is_head_of_free_region(page))) {
		*zone_pfn += chunk_size - 1;
		return 0;
	}

	return 1;
}

static int count_data_pages(callback_t callback, unsigned long data)
{
	struct zone *zone;
	unsigned long zone_pfn, tmp_pfn;
	unsigned long phys, virt;
	int ret;
	
	for_each_zone(zone) {
		if (!is_highmem(zone)) {
			for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn)
				if(saveable(zone, &zone_pfn)) {
					tmp_pfn = zone_pfn + zone->zone_start_pfn;
					virt = (unsigned long)page_address(pfn_to_page(tmp_pfn));
					phys = virt_to_phys((void *)virt);
					if((ret=callback(phys, virt, DOING, data)))
						return ret;
				}
		}
	}

	return 0;
}

static int count_pages(callback_t callback, unsigned long data)
{
	int ret;
	
	if((ret=callback(0, 0, START, data)))
		return ret;

	if((ret=count_data_pages(callback, data)))
		return ret;
	
	if((ret=count_ext_pages(callback, data)))
		return ret;
	
	if((ret=callback(0, 0, END, data)))
		return ret;

	return 0;
}

static void calc_cksum(unsigned long *cksum, unsigned long *buf, unsigned long size)
{
	int i;

	for(i=0; i<size/4; i++){
		*cksum ^= buf[i];
	}
}

static void recalc_cksum(unsigned long *cksum, unsigned long *buf, unsigned long size)
{
	if(!(default_flags() & WBI_CKSUM)) return;
	if(!cksum) return;
	calc_cksum(cksum, buf, size);
}

static int send_data(void *b, unsigned long len, unsigned long *cksum)
{
	unsigned char *buf = b;
	unsigned long sendbuf_size = wb_default_device->sector_size;
	int ret;
	
	while(len){
		unsigned long tmp = sendbuf_size - buf_top;

		tmp = tmp>len?len:tmp;
		memcpy(sendbuf+buf_top, buf, tmp);
		recalc_cksum(cksum, (unsigned long *)(sendbuf+buf_top), tmp);

		buf_top += tmp;
		buf += tmp;
		if(buf_top==sendbuf_size){
			if((ret=flush_sendbuf()))
				return ret;
		}
		
		len -= tmp;
	}
	return 0;
}

static int align_sendbuf(unsigned long alignment)
{
	unsigned long old_buf_top = buf_top;
	unsigned long sendbuf_size = wb_default_device->sector_size;
	
	buf_top = RU_SIZE(buf_top, alignment);
	memset(sendbuf+old_buf_top, 0, buf_top-old_buf_top);
	
	if(buf_top==sendbuf_size)
		return flush_sendbuf();
	return 0;
}

static unsigned char first_flush=0;

static int flush_sendbuf(void)
{
	unsigned long sendbuf_size = wb_default_device->sector_size;
	unsigned long cur = cur_sector;
	unsigned long nsect = sendbuf_size>>9;
	/*if no date in sendbuf then just return*/
	if(!buf_top)
		return 0;

	if(buf_top != sendbuf_size)
		memset(sendbuf+buf_top, 0x00, sendbuf_size-buf_top);

	if(first_flush){//save for backward overwrite
		memcpy(sendbuf+sendbuf_size, sendbuf, sendbuf_size);
		first_flush = 0;
	}
	
	buf_top = 0;
	cur_sector += nsect;
	return wb_default_device->write_sector(wb_default_device, sendbuf, cur, nsect);
}

//#define MAX_SECTIONS 1024

static unsigned long cur_page, cur_page_virt, cur_section, sections, pages;
static unsigned long section_data_order;
static struct wb_section *section_data;

#define INIT_CKSUM(cksum) do{(cksum) = 0x0F0F0F0F;}while(0)

static int count_sections_p1(unsigned long phys, unsigned long virt, unsigned long flag, unsigned long data)
{
	if(flag==START){
		cur_page = -1UL;
		cur_page_virt = -1UL;
		sections = 0;
		pages = 0;
		return 0;
	}
	else if(flag==END){
		/*
		  we append 1 section for the possibility of new section
		  because of section_data memory allocation
		 */
		sections ++;

		if(section_data){
			wb_msg("freeing section_data: %p\n", section_data);
			free_pages((unsigned long)section_data, section_data_order);
			section_data = NULL;
		}

		section_data_order = PAGE_ALIGN(sections*sizeof(*section_data))>>PAGE_SHIFT;
		section_data_order = get_bitmask_order(section_data_order - 1);
		section_data = (struct wb_section *)__get_free_pages(GFP_ATOMIC, section_data_order);
		wb_msg("allocating section_data: %p, %ld %ld\n", section_data, section_data_order, pages);
		if(!section_data){
			wb_msg("section_data allocation failed\n");
			return -ENOMEM;
		}

		pages += (1<<section_data_order);
		wb_msg("%s:%ld, %ld\n", __FUNCTION__, sections, pages);

		flush_cache_all();
		return 0;
	}

	// a new section, we consider physical & virtual continue area to be one section
	if(phys != (cur_page+PAGE_SIZE) || virt != (cur_page_virt+PAGE_SIZE)){
		sections ++;
	}

	cur_page = phys;
	cur_page_virt = virt;
	
	pages ++;
	return 0;
}

static unsigned long calc_o_image_size(struct wb_header *header)
{
	unsigned long image_header = RU_SECTOR_SIZE(sizeof(*header));
	unsigned long section_headers = RU_SECTOR_SIZE(header->sections*sizeof(struct wb_section));
	
	return (image_header + section_headers + header->o_data_size);}

static unsigned long calc_r_image_size(struct wb_header *header)
{
	unsigned long image_header = RU_SECTOR_SIZE(sizeof(*header));
	unsigned long section_headers = RU_SECTOR_SIZE(header->sections*sizeof(struct wb_section));

	/*
	  it is hard to estimate the size of compressed-WBI in current stage,
	  and we just suppose the maximum deflation rate to be 50%
	  so it may fail during the tranfer of WBI because of size-overflow
	  even though the capacity check is passed
	 */
	if(default_flags() & WBI_COMPRESS){
		if(header->o_data_size == header->r_data_size)
			return (image_header + section_headers + (header->o_data_size>>1));
		else
			return (image_header + section_headers + header->r_data_size);
	}
	
	return (image_header + section_headers + header->o_data_size);
}

static int count_sections_p2(unsigned long phys, unsigned long virt, unsigned long flag, unsigned long data)
{
	struct wb_header *header = (struct wb_header *)data;
	
	if(flag==START){
		cur_page = -1UL;
		cur_page_virt = -1UL;
		cur_section = -1UL;
		sections = 0;
		pages = 0;
		return 0;
	}
	else if(flag==END){
		header->sections = sections;
		header->o_data_size = pages*PAGE_SIZE;
		header->r_data_size = header->o_data_size;
		INIT_CKSUM(header->kern_cksum);

		if(default_flags() & WBI_NOT_KERNEL_RO){
			unsigned long kn_pfn_start = PAGE_ALIGN(__pa(&_kern_text_start));
			unsigned long kn_pfn_end = __pa(&_kern_text_end)&PAGE_MASK;

			header->kern_start = kn_pfn_start;
			header->kern_size = (kn_pfn_end-kn_pfn_start);
			recalc_cksum(&header->kern_cksum,
				   phys_to_virt(kn_pfn_start),
				   (kn_pfn_end-kn_pfn_start));
			
			wb_msg("%s:%08lx,%08lx -> %08lx\n", __FUNCTION__, kn_pfn_start, kn_pfn_end, header->kern_cksum);
		}
		
		wb_msg("%s:%ld, %ld, %08lx(%08lx)\n", __FUNCTION__, header->sections, pages,
		       calc_o_image_size(header), calc_r_image_size(header));

		return 0;
	}
	
	if(phys != (cur_page+PAGE_SIZE) || virt != (cur_page_virt+PAGE_SIZE)){// a new section
		sections ++;
		cur_section ++;
		
		INIT_CKSUM(section_data[cur_section].cksum);
		section_data[cur_section].addr = phys;
		section_data[cur_section].olen = 0;
		section_data[cur_section].flag = 0;
		section_data[cur_section].rlen = 0;
		section_data[cur_section].virt = virt;
	}

	cur_page = phys;
	cur_page_virt = virt;
	
	pages ++;
	section_data[cur_section].olen += PAGE_SIZE;
	
	return 0;
}

#define LZ77_STEP (PAGE_SIZE>>1)		
static u8 *lz77_wk;

static int wbi_send_compressed_page(unsigned char *virt, struct wb_section *section)
{
	unsigned long sendbuf_size = wb_default_device->sector_size;
	unsigned long *cksum = &section->cksum;
	int len=0;
	unsigned char *s = c_buff;
	unsigned char *se = c_buff+PAGE_SIZE;
	unsigned char *d;
	unsigned char *de;
	int ret;
	
	/*copy PAGE data to buffer, we can defrence directly
	  because data could change during operation*/
	memcpy(c_buff, virt, PAGE_SIZE);
	recalc_cksum(cksum, (unsigned long *)c_buff, PAGE_SIZE);

	if(!(default_flags() & WBI_LZ77_COMPRESS)){
		d = c_buff+PAGE_SIZE;
		de =  c_buff+C_BUFFSIZE;

		len = rle_deflate(s, se-s, d, de-d);
		if((ret=send_data(d, len, NULL)))
			return ret;
	}
	else{
		int tlen;
		while(s<se){
			if(likely(sendbuf_size-buf_top>(LZ77_STEP+4))){
				d = sendbuf+buf_top;
				de =  sendbuf+sendbuf_size;

				tlen = lz77_deflate(s, LZ77_STEP, d, de-d, lz77_wk, LZ77_WORKSPACE_SIZE(LZ77_STEP));
				len += tlen;
				buf_top += tlen;
			}
			else{
				d = c_buff+PAGE_SIZE;
				de =  c_buff+C_BUFFSIZE;

				tlen = lz77_deflate(s, LZ77_STEP, d, de-d, lz77_wk, LZ77_WORKSPACE_SIZE(LZ77_STEP));
				len += tlen;
				if((ret=send_data(d, tlen, NULL)))
					return ret;
			}
			
			s +=  LZ77_STEP;
		}
		
	}

	section->rlen += len;
	return 0;
}

static int wbi_send_section(struct wb_section *section)
{
	unsigned char *virt = (void *)section->virt;
	unsigned char *virt_end = virt + section->olen;
	int ret=0;

	while(virt<virt_end){
		if(!(default_flags() & WBI_COMPRESS))
			ret = send_data((void *)virt, PAGE_SIZE, &section->cksum);
		else/*do compression*/
			ret = wbi_send_compressed_page((unsigned char *)virt, section);
		if(ret)
			return ret;

		virt += PAGE_SIZE;
	}

	return 0;
}

static int send_section_pages(struct wb_header *header)
{
	unsigned long i;
	int ret;
	
	/*send section data*/
	header->r_data_size = 0;
	for(i=0; i<sections; i++){
		struct wb_section *section = &section_data[i];

		if((ret=wbi_send_section(section)))
			return ret;
		
		section->rlen = section->rlen?RU_SECTOR_SIZE(section->rlen):section->olen;
		
		INIT_CKSUM(section->meta_cksum);
		calc_cksum(&section->meta_cksum, (void *)section, sizeof(*section)-sizeof(unsigned long));
		
		wb_printk("%s: %3ld %08lx:%08lx:%08lx %08lx<->%08lx\n",
			 __FUNCTION__, i, section->addr, 
			 section->cksum, section->flag,
			 section->olen, section->rlen);

		header->r_data_size += section->rlen;

		if((ret=align_sendbuf(header->sector_size)))
			return ret;
	}
	/*send section meta data*/
	if((ret=send_data(section_data, sizeof(*section_data)*sections, NULL)))
		return ret;
	if((ret=align_sendbuf(header->sector_size)))
		return ret;
	
	wb_msg("%s: pages - %ld %08lx<->%08lx\n", __FUNCTION__, pages,
	       calc_o_image_size(header), calc_r_image_size(header));
	return 0;
}

static int do_make_image(unsigned long entry, unsigned long adjust_vma)
{
	int ret = 0;
	int retried = 0;
	struct wb_header header = {
		.fVersion = {'W','B','I','1'},
		.dVersion = CONFIG_WBI_DVERSION,
		.sections = 0,
		.flag = default_flags(),
		.resume_vector = virt_to_phys((void *)entry),
		.sector_size = wb_default_device->phys_sector_size,
		.o_data_size = 0,
		.r_data_size = 0
	};

 retry:
	if(wb_default_device->open)
		if((ret=wb_default_device->open(wb_default_device)<0))
			goto send_error;

	do {
		unsigned long image_size;
		
		if(default_flags() & WBI_COMPRESS)
			first_flush = 1;
		else
			first_flush = 0;
	
		cur_sector = 0;
	
		/*phase 1: estimate sections*/
		if((ret=count_pages(count_sections_p1, (unsigned long)&header)))
			goto send_error;
		/*
		  phase 2:
		  count real sections,
		  fill section meta data,
		*/
		if((ret=count_pages(count_sections_p2, (unsigned long)&header)))
			goto send_error;

		image_size = calc_r_image_size(&header);
		if(wb_default_device->capacity
		   && wb_default_device->capacity<image_size){
			unsigned long shrink_pages = image_size - wb_default_device->capacity;
			unsigned long shrinked_pages = 0, to_shrink, tmp;
			
			ret = -ENOSPC;
			if (image_mode != IMAGE_MODE_ADP) {
				wb_msg("error: %s image size greater than device capacity!\n", __FUNCTION__);
				goto send_error;
			}

			shrink_pages = PAGE_ALIGN(shrink_pages)>>PAGE_SHIFT;
			to_shrink = shrink_pages;

			wb_msg("Image too huge!\n");
			wb_msg("Trying to free %lu pages:", shrink_pages);
			
			while (to_shrink && (tmp=shrink_all_memory(to_shrink))) {
				shrinked_pages += tmp;
				to_shrink -= tmp;
			}
			
			wb_msg("%lu freed -> %s\n", shrinked_pages, shrinked_pages>=shrink_pages?"OK":"FAIL");

			if (shrinked_pages>=shrink_pages)
				continue;

			goto send_error;
		}

		break;
	} while(1);

	/* send image header */
	if((ret=send_data(&header, sizeof(header), NULL)) ||
		(ret=align_sendbuf(header.sector_size)))
		goto send_error;
		
	/*
	  phase 2:
	  send section
	  and section meta data
	*/
	if((ret=send_section_pages(&header)))
		goto send_error;

	if((ret=flush_sendbuf()))
		goto send_error;

	if(header.o_data_size != header.r_data_size){
		u32 sendbuf_size = wb_default_device->sector_size;
		u32 nsect =  sendbuf_size>>9;
		
		wb_msg("rewrite header for compressed image!\n");
		memcpy(sendbuf, sendbuf+sendbuf_size, sendbuf_size);
		send_data(&header, sizeof(header), NULL);
		wb_default_device->write_sector(wb_default_device, sendbuf, 0, nsect);
	}
	
	if(wb_default_device->close)
		if((ret=wb_default_device->close(wb_default_device)))
			goto close_error;

	return 0;
	
 send_error:
	if(wb_default_device->close)
		wb_default_device->close(wb_default_device);

	if(!retried && ret == -ENOSPC && default_flags() & WBI_COMPRESS){
		retried = 1;

		wb_msg("NO SPACE error!\n");
		wb_msg("Will free memory, and retry.\n");
		free_some_memory(IMAGE_MODE_MIN);
		goto retry;
	}

	/* erase the header*/
	if(wb_default_device->open)
		wb_default_device->open(wb_default_device);
	
	memset(&header, 0, sizeof(header));
	send_data(&header, sizeof(header), NULL);
	flush_sendbuf();
	
	if(wb_default_device->close)
		wb_default_device->close(wb_default_device);
 close_error:

	printk("%s: SUSPNED is aborted - error code (%d)!\n", __FUNCTION__, ret);
	return ret;
}

/*
  to disable the Memory Dump from Warm-boot-image booted kernel
 */
int cxd_create_warmbootimage(unsigned long entry, unsigned long adjust_vma)
{
	if(!WBI_MODE(pm_state, image_mode) || !sendbuf)
		return 0;

	free_some_memory(image_mode);

	return do_make_image(entry, adjust_vma);
}

/*
  interfaces to warmboot storager driver
 */
int wb_register_device(struct wb_device *device,int def)
{
	if(!device)
		return -ENODEV;
	
	down(&wb_device_sem);
	
	list_add_tail(&device->list, &wb_device_list);
	if(!wb_default_device || def)
		wb_default_device = device;

	if(device->phys_sector_size==0)
		device->phys_sector_size = DEFAULT_SECTOR_SIZE;

	if(device->sector_size<512){
		device->sector_size = 512;
		printk("Warning: set sector size of %s to 512!\n", device->name);
	}
	
	up(&wb_device_sem);

	return 0;
}

int wb_unregister_device(struct wb_device *device)
{
	struct list_head *ptr;

	if(!device)
		return -ENODEV;
	
	down(&wb_device_sem);
	
	list_del(&device->list);
	if(list_empty(&wb_device_list)){
		wb_default_device = NULL;
	}
	else if(wb_default_device == device){
		ptr = wb_device_list.next;
		wb_default_device = list_entry(ptr, struct wb_device, list);
	}

	up(&wb_device_sem);

	return 0;
}

int warm_boot_pm_prepare(suspend_state_t state)
{
	unsigned long wksize;
	int ret = 0;
	
	pm_state = state;
	
	if(!WBI_MODE(state, image_mode) || !wb_default_device)
		return 0;

	wksize = wb_default_device->sector_size;
	if(wb_default_device->suspend_prepare)
		if((ret=wb_default_device->suspend_prepare(wb_default_device)))
			goto error;
	
	if(default_flags() & WBI_COMPRESS){
		wksize *= 2;
		
		c_buff = vmalloc(C_BUFFSIZE);
		if(!c_buff){
			printk("allocate c_buff error!\n");
			ret = -ENOMEM;
			goto error;
		}
		wb_msg("c_buff: %8p, %08lx\n", c_buff, C_BUFFSIZE);
	}

	if(default_flags() & WBI_LZ77_COMPRESS){
		lz77_wk = vmalloc(LZ77_WORKSPACE_SIZE(LZ77_STEP));
		if(!lz77_wk){
			printk("allocate lz77_wk error!\n");
			ret = -ENOMEM;
			goto error;
		}
		wb_msg("lz77_wk: %8p, %08lx\n", lz77_wk, LZ77_WORKSPACE_SIZE(LZ77_STEP));
	}

	wb_msg("workspace size for WBI: %08lx\n", wksize);
	buf_top = 0;
	if(!sendbuf){
		sendbuf = vmalloc(wksize);
		if(!sendbuf){
			printk("allocate memory failed!\r\n");
			printk("Warm Boot Image wont be created!\r\n");
			ret = -ENOMEM;
			goto error;
		}
		wb_msg("allocating sendbuf: %p\n", sendbuf);
	}
	

	if(ioremap_ext_pages())
		goto error;

	return 0;

 error:
	warm_boot_pm_finish(state);
	return ret;
}

int warm_boot_pm_finish(suspend_state_t state)
{
	if(!WBI_MODE(state, image_mode) || !wb_default_device)
		return 0;

	iounmap_ext_pages();
	
	image_mode=IMAGE_MODE_NONE;
	
	if(section_data){
		wb_msg("freeing section_data: %p\n", section_data);
		free_pages((unsigned long)section_data, section_data_order);
		section_data = NULL;
	}
		
	if(sendbuf){
		wb_msg("freeing sendbuf: %p\n", sendbuf);
		vfree(sendbuf);
		sendbuf = NULL;
	}

	if(lz77_wk){
		wb_msg("freeing lz77_wk: %p\n", lz77_wk);
		vfree(lz77_wk);
		lz77_wk = NULL;
	}

	if(c_buff){
		wb_msg("freeing c_buff: %p\n", c_buff);
		vfree(c_buff);
		c_buff = NULL;
	}
	
	if(wb_default_device->suspend_finish)
		return wb_default_device->suspend_finish(wb_default_device);

	return 0;
}

suspend_state_t pm_get_state(void)
{
	return pm_state;
}

EXPORT_SYMBOL(wb_register_device);
EXPORT_SYMBOL(wb_unregister_device);
EXPORT_SYMBOL(pm_get_state);

