2005-11-07 14:15:26 +03:00
/*
2005-04-17 02:20:36 +04:00
* Direct MTD block device access
*
* ( C ) 2000 - 2003 Nicolas Pitre < nico @ cam . org >
* ( C ) 1999 - 2003 David Woodhouse < dwmw2 @ infradead . org >
*/
# include <linux/fs.h>
# include <linux/init.h>
2005-11-07 02:14:42 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/sched.h>
2005-04-17 02:20:36 +04:00
# include <linux/slab.h>
2005-11-07 02:14:42 +03:00
# include <linux/types.h>
2005-04-17 02:20:36 +04:00
# include <linux/vmalloc.h>
2005-11-07 02:14:42 +03:00
2005-04-17 02:20:36 +04:00
# include <linux/mtd/mtd.h>
# include <linux/mtd/blktrans.h>
2006-03-31 14:29:41 +04:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
static struct mtdblk_dev {
struct mtd_info * mtd ;
int count ;
2006-03-31 14:29:41 +04:00
struct mutex cache_mutex ;
2005-04-17 02:20:36 +04:00
unsigned char * cache_data ;
unsigned long cache_offset ;
unsigned int cache_size ;
enum { STATE_EMPTY , STATE_CLEAN , STATE_DIRTY } cache_state ;
} * mtdblks [ MAX_MTD_DEVICES ] ;
2009-07-15 00:04:29 +04:00
static struct mutex mtdblks_lock ;
2005-04-17 02:20:36 +04:00
/*
* Cache stuff . . .
2005-11-07 14:15:26 +03:00
*
2005-04-17 02:20:36 +04:00
* Since typical flash erasable sectors are much larger than what Linux ' s
* buffer cache can handle , we must implement read - modify - write on flash
* sectors for each block write requests . To avoid over - erasing flash sectors
* and to speed things up , we locally cache a whole flash sector while it is
* being written to until a different sector is required .
*/
static void erase_callback ( struct erase_info * done )
{
wait_queue_head_t * wait_q = ( wait_queue_head_t * ) done - > priv ;
wake_up ( wait_q ) ;
}
2005-11-07 14:15:26 +03:00
static int erase_write ( struct mtd_info * mtd , unsigned long pos ,
2005-04-17 02:20:36 +04:00
int len , const char * buf )
{
struct erase_info erase ;
DECLARE_WAITQUEUE ( wait , current ) ;
wait_queue_head_t wait_q ;
size_t retlen ;
int ret ;
/*
* First , let ' s erase the flash block .
*/
init_waitqueue_head ( & wait_q ) ;
erase . mtd = mtd ;
erase . callback = erase_callback ;
erase . addr = pos ;
erase . len = len ;
erase . priv = ( u_long ) & wait_q ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
add_wait_queue ( & wait_q , & wait ) ;
2006-05-28 13:01:53 +04:00
ret = mtd - > erase ( mtd , & erase ) ;
2005-04-17 02:20:36 +04:00
if ( ret ) {
set_current_state ( TASK_RUNNING ) ;
remove_wait_queue ( & wait_q , & wait ) ;
printk ( KERN_WARNING " mtdblock: erase of region [0x%lx, 0x%x] "
" on \" %s \" failed \n " ,
pos , len , mtd - > name ) ;
return ret ;
}
schedule ( ) ; /* Wait for erase to finish. */
remove_wait_queue ( & wait_q , & wait ) ;
/*
* Next , writhe data to flash .
*/
2006-05-28 13:01:53 +04:00
ret = mtd - > write ( mtd , pos , len , & retlen , buf ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
return ret ;
if ( retlen ! = len )
return - EIO ;
return 0 ;
}
static int write_cached_data ( struct mtdblk_dev * mtdblk )
{
struct mtd_info * mtd = mtdblk - > mtd ;
int ret ;
if ( mtdblk - > cache_state ! = STATE_DIRTY )
return 0 ;
DEBUG ( MTD_DEBUG_LEVEL2 , " mtdblock: writing cached data for \" %s \" "
2005-11-07 14:15:26 +03:00
" at 0x%lx, size 0x%x \n " , mtd - > name ,
2005-04-17 02:20:36 +04:00
mtdblk - > cache_offset , mtdblk - > cache_size ) ;
2005-11-07 14:15:26 +03:00
ret = erase_write ( mtd , mtdblk - > cache_offset ,
2005-04-17 02:20:36 +04:00
mtdblk - > cache_size , mtdblk - > cache_data ) ;
if ( ret )
return ret ;
/*
* Here we could argubly set the cache state to STATE_CLEAN .
2005-11-07 14:15:26 +03:00
* However this could lead to inconsistency since we will not
* be notified if this content is altered on the flash by other
2005-04-17 02:20:36 +04:00
* means . Let ' s declare it empty and leave buffering tasks to
* the buffer cache instead .
*/
mtdblk - > cache_state = STATE_EMPTY ;
return 0 ;
}
2005-11-07 14:15:26 +03:00
static int do_cached_write ( struct mtdblk_dev * mtdblk , unsigned long pos ,
2005-04-17 02:20:36 +04:00
int len , const char * buf )
{
struct mtd_info * mtd = mtdblk - > mtd ;
unsigned int sect_size = mtdblk - > cache_size ;
size_t retlen ;
int ret ;
DEBUG ( MTD_DEBUG_LEVEL2 , " mtdblock: write on \" %s \" at 0x%lx, size 0x%x \n " ,
mtd - > name , pos , len ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
if ( ! sect_size )
2006-05-28 13:01:53 +04:00
return mtd - > write ( mtd , pos , len , & retlen , buf ) ;
2005-04-17 02:20:36 +04:00
while ( len > 0 ) {
unsigned long sect_start = ( pos / sect_size ) * sect_size ;
unsigned int offset = pos - sect_start ;
unsigned int size = sect_size - offset ;
2005-11-07 14:15:26 +03:00
if ( size > len )
2005-04-17 02:20:36 +04:00
size = len ;
if ( size = = sect_size ) {
2005-11-07 14:15:26 +03:00
/*
2005-04-17 02:20:36 +04:00
* We are covering a whole sector . Thus there is no
* need to bother with the cache while it may still be
* useful for other partial writes .
*/
ret = erase_write ( mtd , pos , size , buf ) ;
if ( ret )
return ret ;
} else {
/* Partial sector: need to use the cache */
if ( mtdblk - > cache_state = = STATE_DIRTY & &
mtdblk - > cache_offset ! = sect_start ) {
ret = write_cached_data ( mtdblk ) ;
2005-11-07 14:15:26 +03:00
if ( ret )
2005-04-17 02:20:36 +04:00
return ret ;
}
if ( mtdblk - > cache_state = = STATE_EMPTY | |
mtdblk - > cache_offset ! = sect_start ) {
/* fill the cache with the current sector */
mtdblk - > cache_state = STATE_EMPTY ;
2006-05-28 13:01:53 +04:00
ret = mtd - > read ( mtd , sect_start , sect_size ,
& retlen , mtdblk - > cache_data ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
return ret ;
if ( retlen ! = sect_size )
return - EIO ;
mtdblk - > cache_offset = sect_start ;
mtdblk - > cache_size = sect_size ;
mtdblk - > cache_state = STATE_CLEAN ;
}
/* write data to our local cache */
memcpy ( mtdblk - > cache_data + offset , buf , size ) ;
mtdblk - > cache_state = STATE_DIRTY ;
}
buf + = size ;
pos + = size ;
len - = size ;
}
return 0 ;
}
2005-11-07 14:15:26 +03:00
static int do_cached_read ( struct mtdblk_dev * mtdblk , unsigned long pos ,
2005-04-17 02:20:36 +04:00
int len , char * buf )
{
struct mtd_info * mtd = mtdblk - > mtd ;
unsigned int sect_size = mtdblk - > cache_size ;
size_t retlen ;
int ret ;
2005-11-07 14:15:26 +03:00
DEBUG ( MTD_DEBUG_LEVEL2 , " mtdblock: read on \" %s \" at 0x%lx, size 0x%x \n " ,
2005-04-17 02:20:36 +04:00
mtd - > name , pos , len ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
if ( ! sect_size )
2006-05-28 13:01:53 +04:00
return mtd - > read ( mtd , pos , len , & retlen , buf ) ;
2005-04-17 02:20:36 +04:00
while ( len > 0 ) {
unsigned long sect_start = ( pos / sect_size ) * sect_size ;
unsigned int offset = pos - sect_start ;
unsigned int size = sect_size - offset ;
2005-11-07 14:15:26 +03:00
if ( size > len )
2005-04-17 02:20:36 +04:00
size = len ;
/*
* Check if the requested data is already cached
* Read the requested amount of data from our internal cache if it
* contains what we want , otherwise we read the data directly
* from flash .
*/
if ( mtdblk - > cache_state ! = STATE_EMPTY & &
mtdblk - > cache_offset = = sect_start ) {
memcpy ( buf , mtdblk - > cache_data + offset , size ) ;
} else {
2006-05-28 13:01:53 +04:00
ret = mtd - > read ( mtd , pos , size , & retlen , buf ) ;
2005-04-17 02:20:36 +04:00
if ( ret )
return ret ;
if ( retlen ! = size )
return - EIO ;
}
buf + = size ;
pos + = size ;
len - = size ;
}
return 0 ;
}
static int mtdblock_readsect ( struct mtd_blktrans_dev * dev ,
unsigned long block , char * buf )
{
struct mtdblk_dev * mtdblk = mtdblks [ dev - > devnum ] ;
return do_cached_read ( mtdblk , block < < 9 , 512 , buf ) ;
}
static int mtdblock_writesect ( struct mtd_blktrans_dev * dev ,
unsigned long block , char * buf )
{
struct mtdblk_dev * mtdblk = mtdblks [ dev - > devnum ] ;
if ( unlikely ( ! mtdblk - > cache_data & & mtdblk - > cache_size ) ) {
mtdblk - > cache_data = vmalloc ( mtdblk - > mtd - > erasesize ) ;
if ( ! mtdblk - > cache_data )
return - EINTR ;
/* -EINTR is not really correct, but it is the best match
* documented in man 2 write for all cases . We could also
* return - EAGAIN sometimes , but why bother ?
*/
}
return do_cached_write ( mtdblk , block < < 9 , 512 , buf ) ;
}
static int mtdblock_open ( struct mtd_blktrans_dev * mbd )
{
struct mtdblk_dev * mtdblk ;
struct mtd_info * mtd = mbd - > mtd ;
int dev = mbd - > devnum ;
DEBUG ( MTD_DEBUG_LEVEL1 , " mtdblock_open \n " ) ;
2005-11-07 14:15:26 +03:00
2009-07-15 00:04:29 +04:00
mutex_lock ( & mtdblks_lock ) ;
2005-04-17 02:20:36 +04:00
if ( mtdblks [ dev ] ) {
mtdblks [ dev ] - > count + + ;
2009-07-15 00:04:29 +04:00
mutex_unlock ( & mtdblks_lock ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
/* OK, it's not open. Create cache info for it */
2006-11-15 22:10:29 +03:00
mtdblk = kzalloc ( sizeof ( struct mtdblk_dev ) , GFP_KERNEL ) ;
2009-07-15 00:04:29 +04:00
if ( ! mtdblk ) {
mutex_unlock ( & mtdblks_lock ) ;
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2009-07-15 00:04:29 +04:00
}
2005-04-17 02:20:36 +04:00
mtdblk - > count = 1 ;
mtdblk - > mtd = mtd ;
2006-03-31 14:29:41 +04:00
mutex_init ( & mtdblk - > cache_mutex ) ;
2005-04-17 02:20:36 +04:00
mtdblk - > cache_state = STATE_EMPTY ;
2006-05-30 16:25:24 +04:00
if ( ! ( mtdblk - > mtd - > flags & MTD_NO_ERASE ) & & mtdblk - > mtd - > erasesize ) {
2005-04-17 02:20:36 +04:00
mtdblk - > cache_size = mtdblk - > mtd - > erasesize ;
mtdblk - > cache_data = NULL ;
}
mtdblks [ dev ] = mtdblk ;
2009-07-15 00:04:29 +04:00
mutex_unlock ( & mtdblks_lock ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
DEBUG ( MTD_DEBUG_LEVEL1 , " ok \n " ) ;
return 0 ;
}
static int mtdblock_release ( struct mtd_blktrans_dev * mbd )
{
int dev = mbd - > devnum ;
struct mtdblk_dev * mtdblk = mtdblks [ dev ] ;
DEBUG ( MTD_DEBUG_LEVEL1 , " mtdblock_release \n " ) ;
2009-07-15 00:04:29 +04:00
mutex_lock ( & mtdblks_lock ) ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtdblk - > cache_mutex ) ;
2005-04-17 02:20:36 +04:00
write_cached_data ( mtdblk ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtdblk - > cache_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( ! - - mtdblk - > count ) {
/* It was the last usage. Free the device */
mtdblks [ dev ] = NULL ;
if ( mtdblk - > mtd - > sync )
mtdblk - > mtd - > sync ( mtdblk - > mtd ) ;
vfree ( mtdblk - > cache_data ) ;
kfree ( mtdblk ) ;
}
2009-07-15 00:04:29 +04:00
mutex_unlock ( & mtdblks_lock ) ;
2005-04-17 02:20:36 +04:00
DEBUG ( MTD_DEBUG_LEVEL1 , " ok \n " ) ;
return 0 ;
2005-11-07 14:15:26 +03:00
}
2005-04-17 02:20:36 +04:00
static int mtdblock_flush ( struct mtd_blktrans_dev * dev )
{
struct mtdblk_dev * mtdblk = mtdblks [ dev - > devnum ] ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtdblk - > cache_mutex ) ;
2005-04-17 02:20:36 +04:00
write_cached_data ( mtdblk ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtdblk - > cache_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( mtdblk - > mtd - > sync )
mtdblk - > mtd - > sync ( mtdblk - > mtd ) ;
return 0 ;
}
static void mtdblock_add_mtd ( struct mtd_blktrans_ops * tr , struct mtd_info * mtd )
{
2006-11-15 22:10:29 +03:00
struct mtd_blktrans_dev * dev = kzalloc ( sizeof ( * dev ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! dev )
return ;
dev - > mtd = mtd ;
dev - > devnum = mtd - > index ;
2006-10-27 12:09:33 +04:00
2005-04-17 02:20:36 +04:00
dev - > size = mtd - > size > > 9 ;
dev - > tr = tr ;
if ( ! ( mtd - > flags & MTD_WRITEABLE ) )
dev - > readonly = 1 ;
add_mtd_blktrans_dev ( dev ) ;
}
static void mtdblock_remove_dev ( struct mtd_blktrans_dev * dev )
{
del_mtd_blktrans_dev ( dev ) ;
kfree ( dev ) ;
}
static struct mtd_blktrans_ops mtdblock_tr = {
. name = " mtdblock " ,
. major = 31 ,
. part_bits = 0 ,
2006-10-27 12:09:33 +04:00
. blksize = 512 ,
2005-04-17 02:20:36 +04:00
. open = mtdblock_open ,
. flush = mtdblock_flush ,
. release = mtdblock_release ,
. readsect = mtdblock_readsect ,
. writesect = mtdblock_writesect ,
. add_mtd = mtdblock_add_mtd ,
. remove_dev = mtdblock_remove_dev ,
. owner = THIS_MODULE ,
} ;
static int __init init_mtdblock ( void )
{
2009-07-15 00:04:29 +04:00
mutex_init ( & mtdblks_lock ) ;
2005-04-17 02:20:36 +04:00
return register_mtd_blktrans ( & mtdblock_tr ) ;
}
static void __exit cleanup_mtdblock ( void )
{
deregister_mtd_blktrans ( & mtdblock_tr ) ;
}
module_init ( init_mtdblock ) ;
module_exit ( cleanup_mtdblock ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Nicolas Pitre <nico@cam.org> et al. " ) ;
MODULE_DESCRIPTION ( " Caching read/erase/writeback block device emulation access to MTD devices " ) ;