2005-04-17 02:20:36 +04:00
/*
2005-11-07 14:15:26 +03:00
* $ Id : mtd_blkdevs . c , v 1.27 2005 / 11 / 07 11 : 14 : 20 gleixner Exp $
2005-04-17 02:20:36 +04:00
*
* ( C ) 2003 David Woodhouse < dwmw2 @ infradead . org >
*
* Interface to Linux 2.5 block layer for MTD ' translation layers ' .
*
*/
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/list.h>
# include <linux/fs.h>
# include <linux/mtd/blktrans.h>
# include <linux/mtd/mtd.h>
# include <linux/blkdev.h>
# include <linux/blkpg.h>
2007-07-17 15:03:35 +04:00
# include <linux/freezer.h>
2005-04-17 02:20:36 +04:00
# include <linux/spinlock.h>
# include <linux/hdreg.h>
# include <linux/init.h>
2006-03-31 14:29:41 +04:00
# include <linux/mutex.h>
2007-04-19 11:58:33 +04:00
# include <linux/kthread.h>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
2007-05-28 23:28:34 +04:00
# include "mtdcore.h"
2005-04-17 02:20:36 +04:00
2007-05-28 23:28:34 +04:00
static LIST_HEAD ( blktrans_majors ) ;
2005-04-17 02:20:36 +04:00
struct mtd_blkcore_priv {
2007-04-22 23:40:57 +04:00
struct task_struct * thread ;
2005-04-17 02:20:36 +04:00
struct request_queue * rq ;
spinlock_t queue_lock ;
} ;
static int do_blktrans_request ( struct mtd_blktrans_ops * tr ,
struct mtd_blktrans_dev * dev ,
struct request * req )
{
unsigned long block , nsect ;
char * buf ;
2006-10-27 12:09:33 +04:00
block = req - > sector < < 9 > > tr - > blkshift ;
nsect = req - > current_nr_sectors < < 9 > > tr - > blkshift ;
2005-04-17 02:20:36 +04:00
buf = req - > buffer ;
2006-08-10 10:44:47 +04:00
if ( ! blk_fs_request ( req ) )
2005-04-17 02:20:36 +04:00
return 0 ;
2006-10-27 12:09:33 +04:00
if ( req - > sector + req - > current_nr_sectors > get_capacity ( req - > rq_disk ) )
2005-04-17 02:20:36 +04:00
return 0 ;
switch ( rq_data_dir ( req ) ) {
case READ :
2006-10-27 12:09:33 +04:00
for ( ; nsect > 0 ; nsect - - , block + + , buf + = tr - > blksize )
2005-04-17 02:20:36 +04:00
if ( tr - > readsect ( dev , block , buf ) )
return 0 ;
return 1 ;
case WRITE :
if ( ! tr - > writesect )
return 0 ;
2006-10-27 12:09:33 +04:00
for ( ; nsect > 0 ; nsect - - , block + + , buf + = tr - > blksize )
2005-04-17 02:20:36 +04:00
if ( tr - > writesect ( dev , block , buf ) )
return 0 ;
return 1 ;
default :
2006-10-01 20:16:00 +04:00
printk ( KERN_NOTICE " Unknown request %u \n " , rq_data_dir ( req ) ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
}
static int mtd_blktrans_thread ( void * arg )
{
struct mtd_blktrans_ops * tr = arg ;
struct request_queue * rq = tr - > blkcore_priv - > rq ;
/* we might get involved when memory gets low, so use PF_MEMALLOC */
2007-07-17 15:03:35 +04:00
current - > flags | = PF_MEMALLOC ;
2005-04-17 02:20:36 +04:00
spin_lock_irq ( rq - > queue_lock ) ;
2007-04-22 23:40:57 +04:00
while ( ! kthread_should_stop ( ) ) {
2005-04-17 02:20:36 +04:00
struct request * req ;
struct mtd_blktrans_dev * dev ;
int res = 0 ;
req = elv_next_request ( rq ) ;
if ( ! req ) {
set_current_state ( TASK_INTERRUPTIBLE ) ;
spin_unlock_irq ( rq - > queue_lock ) ;
schedule ( ) ;
spin_lock_irq ( rq - > queue_lock ) ;
continue ;
}
dev = req - > rq_disk - > private_data ;
tr = dev - > tr ;
spin_unlock_irq ( rq - > queue_lock ) ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & dev - > lock ) ;
2005-04-17 02:20:36 +04:00
res = do_blktrans_request ( tr , dev , req ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & dev - > lock ) ;
2005-04-17 02:20:36 +04:00
spin_lock_irq ( rq - > queue_lock ) ;
end_request ( req , res ) ;
}
spin_unlock_irq ( rq - > queue_lock ) ;
2007-04-22 23:40:57 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static void mtd_blktrans_request ( struct request_queue * rq )
{
struct mtd_blktrans_ops * tr = rq - > queuedata ;
2007-04-22 23:40:57 +04:00
wake_up_process ( tr - > blkcore_priv - > thread ) ;
2005-04-17 02:20:36 +04:00
}
static int blktrans_open ( struct inode * i , struct file * f )
{
struct mtd_blktrans_dev * dev ;
struct mtd_blktrans_ops * tr ;
int ret = - ENODEV ;
dev = i - > i_bdev - > bd_disk - > private_data ;
tr = dev - > tr ;
if ( ! try_module_get ( dev - > mtd - > owner ) )
goto out ;
if ( ! try_module_get ( tr - > owner ) )
goto out_tr ;
2005-11-07 14:15:26 +03:00
/* FIXME: Locking. A hot pluggable device can go away
2005-04-17 02:20:36 +04:00
( del_mtd_device can be called for it ) without its module
being unloaded . */
dev - > mtd - > usecount + + ;
ret = 0 ;
if ( tr - > open & & ( ret = tr - > open ( dev ) ) ) {
dev - > mtd - > usecount - - ;
module_put ( dev - > mtd - > owner ) ;
out_tr :
module_put ( tr - > owner ) ;
}
out :
return ret ;
}
static int blktrans_release ( struct inode * i , struct file * f )
{
struct mtd_blktrans_dev * dev ;
struct mtd_blktrans_ops * tr ;
int ret = 0 ;
dev = i - > i_bdev - > bd_disk - > private_data ;
tr = dev - > tr ;
if ( tr - > release )
ret = tr - > release ( dev ) ;
if ( ! ret ) {
dev - > mtd - > usecount - - ;
module_put ( dev - > mtd - > owner ) ;
module_put ( tr - > owner ) ;
}
return ret ;
}
2006-01-08 12:02:50 +03:00
static int blktrans_getgeo ( struct block_device * bdev , struct hd_geometry * geo )
{
struct mtd_blktrans_dev * dev = bdev - > bd_disk - > private_data ;
if ( dev - > tr - > getgeo )
return dev - > tr - > getgeo ( dev , geo ) ;
return - ENOTTY ;
}
2005-04-17 02:20:36 +04:00
2005-11-07 14:15:26 +03:00
static int blktrans_ioctl ( struct inode * inode , struct file * file ,
2005-04-17 02:20:36 +04:00
unsigned int cmd , unsigned long arg )
{
struct mtd_blktrans_dev * dev = inode - > i_bdev - > bd_disk - > private_data ;
struct mtd_blktrans_ops * tr = dev - > tr ;
switch ( cmd ) {
case BLKFLSBUF :
if ( tr - > flush )
return tr - > flush ( dev ) ;
/* The core code did the work, we had nothing to do. */
return 0 ;
default :
return - ENOTTY ;
}
}
2007-05-28 22:51:59 +04:00
static struct block_device_operations mtd_blktrans_ops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. open = blktrans_open ,
. release = blktrans_release ,
. ioctl = blktrans_ioctl ,
2006-01-08 12:02:50 +03:00
. getgeo = blktrans_getgeo ,
2005-04-17 02:20:36 +04:00
} ;
int add_mtd_blktrans_dev ( struct mtd_blktrans_dev * new )
{
struct mtd_blktrans_ops * tr = new - > tr ;
struct list_head * this ;
int last_devnum = - 1 ;
struct gendisk * gd ;
2007-05-08 11:30:46 +04:00
if ( mutex_trylock ( & mtd_table_mutex ) ) {
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
BUG ( ) ;
}
list_for_each ( this , & tr - > devs ) {
struct mtd_blktrans_dev * d = list_entry ( this , struct mtd_blktrans_dev , list ) ;
if ( new - > devnum = = - 1 ) {
/* Use first free number */
if ( d - > devnum ! = last_devnum + 1 ) {
/* Found a free devnum. Plug it in here */
new - > devnum = last_devnum + 1 ;
list_add_tail ( & new - > list , & d - > list ) ;
goto added ;
}
} else if ( d - > devnum = = new - > devnum ) {
/* Required number taken */
return - EBUSY ;
} else if ( d - > devnum > new - > devnum ) {
/* Required number was free */
list_add_tail ( & new - > list , & d - > list ) ;
goto added ;
2005-11-07 14:15:26 +03:00
}
2005-04-17 02:20:36 +04:00
last_devnum = d - > devnum ;
}
if ( new - > devnum = = - 1 )
new - > devnum = last_devnum + 1 ;
if ( ( new - > devnum < < tr - > part_bits ) > 256 ) {
return - EBUSY ;
}
2006-03-31 14:29:41 +04:00
mutex_init ( & new - > lock ) ;
2005-04-17 02:20:36 +04:00
list_add_tail ( & new - > list , & tr - > devs ) ;
added :
if ( ! tr - > writesect )
new - > readonly = 1 ;
gd = alloc_disk ( 1 < < tr - > part_bits ) ;
if ( ! gd ) {
list_del ( & new - > list ) ;
return - ENOMEM ;
}
gd - > major = tr - > major ;
gd - > first_minor = ( new - > devnum ) < < tr - > part_bits ;
gd - > fops = & mtd_blktrans_ops ;
2005-11-07 14:15:26 +03:00
2005-07-29 23:42:07 +04:00
if ( tr - > part_bits )
if ( new - > devnum < 26 )
snprintf ( gd - > disk_name , sizeof ( gd - > disk_name ) ,
" %s%c " , tr - > name , ' a ' + new - > devnum ) ;
else
snprintf ( gd - > disk_name , sizeof ( gd - > disk_name ) ,
" %s%c%c " , tr - > name ,
' a ' - 1 + new - > devnum / 26 ,
' a ' + new - > devnum % 26 ) ;
else
snprintf ( gd - > disk_name , sizeof ( gd - > disk_name ) ,
" %s%d " , tr - > name , new - > devnum ) ;
2005-04-17 02:20:36 +04:00
/* 2.5 has capacity in units of 512 bytes while still
having BLOCK_SIZE_BITS set to 10. Just to keep us amused . */
2006-10-27 12:09:33 +04:00
set_capacity ( gd , ( new - > size * tr - > blksize ) > > 9 ) ;
2005-04-17 02:20:36 +04:00
gd - > private_data = new ;
new - > blkcore_priv = gd ;
gd - > queue = tr - > blkcore_priv - > rq ;
if ( new - > readonly )
set_disk_ro ( gd , 1 ) ;
add_disk ( gd ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
int del_mtd_blktrans_dev ( struct mtd_blktrans_dev * old )
{
2007-05-08 11:30:46 +04:00
if ( mutex_trylock ( & mtd_table_mutex ) ) {
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
BUG ( ) ;
}
list_del ( & old - > list ) ;
del_gendisk ( old - > blkcore_priv ) ;
put_disk ( old - > blkcore_priv ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void blktrans_notify_remove ( struct mtd_info * mtd )
{
struct list_head * this , * this2 , * next ;
list_for_each ( this , & blktrans_majors ) {
struct mtd_blktrans_ops * tr = list_entry ( this , struct mtd_blktrans_ops , list ) ;
list_for_each_safe ( this2 , next , & tr - > devs ) {
struct mtd_blktrans_dev * dev = list_entry ( this2 , struct mtd_blktrans_dev , list ) ;
if ( dev - > mtd = = mtd )
tr - > remove_dev ( dev ) ;
}
}
}
static void blktrans_notify_add ( struct mtd_info * mtd )
{
struct list_head * this ;
if ( mtd - > type = = MTD_ABSENT )
return ;
list_for_each ( this , & blktrans_majors ) {
struct mtd_blktrans_ops * tr = list_entry ( this , struct mtd_blktrans_ops , list ) ;
tr - > add_mtd ( tr , mtd ) ;
}
}
static struct mtd_notifier blktrans_notifier = {
. add = blktrans_notify_add ,
. remove = blktrans_notify_remove ,
} ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
int register_mtd_blktrans ( struct mtd_blktrans_ops * tr )
{
int ret , i ;
2005-11-07 14:15:26 +03:00
/* Register the notifier if/when the first device type is
2005-04-17 02:20:36 +04:00
registered , to prevent the link / init ordering from fucking
us over . */
if ( ! blktrans_notifier . list . next )
register_mtd_user ( & blktrans_notifier ) ;
2006-11-15 22:10:29 +03:00
tr - > blkcore_priv = kzalloc ( sizeof ( * tr - > blkcore_priv ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! tr - > blkcore_priv )
return - ENOMEM ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
ret = register_blkdev ( tr - > major , tr - > name ) ;
if ( ret ) {
printk ( KERN_WARNING " Unable to register %s block device on major %d: %d \n " ,
tr - > name , tr - > major , ret ) ;
kfree ( tr - > blkcore_priv ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
spin_lock_init ( & tr - > blkcore_priv - > queue_lock ) ;
tr - > blkcore_priv - > rq = blk_init_queue ( mtd_blktrans_request , & tr - > blkcore_priv - > queue_lock ) ;
if ( ! tr - > blkcore_priv - > rq ) {
unregister_blkdev ( tr - > major , tr - > name ) ;
kfree ( tr - > blkcore_priv ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
}
tr - > blkcore_priv - > rq - > queuedata = tr ;
2006-10-27 12:09:33 +04:00
blk_queue_hardsect_size ( tr - > blkcore_priv - > rq , tr - > blksize ) ;
tr - > blkshift = ffs ( tr - > blksize ) - 1 ;
2005-04-17 02:20:36 +04:00
2007-04-22 23:40:57 +04:00
tr - > blkcore_priv - > thread = kthread_run ( mtd_blktrans_thread , tr ,
" %sd " , tr - > name ) ;
if ( IS_ERR ( tr - > blkcore_priv - > thread ) ) {
2005-04-17 02:20:36 +04:00
blk_cleanup_queue ( tr - > blkcore_priv - > rq ) ;
unregister_blkdev ( tr - > major , tr - > name ) ;
kfree ( tr - > blkcore_priv ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2007-04-22 23:40:57 +04:00
return PTR_ERR ( tr - > blkcore_priv - > thread ) ;
2005-11-07 14:15:26 +03:00
}
2005-04-17 02:20:36 +04:00
INIT_LIST_HEAD ( & tr - > devs ) ;
list_add ( & tr - > list , & blktrans_majors ) ;
for ( i = 0 ; i < MAX_MTD_DEVICES ; i + + ) {
if ( mtd_table [ i ] & & mtd_table [ i ] - > type ! = MTD_ABSENT )
tr - > add_mtd ( tr , mtd_table [ i ] ) ;
}
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
int deregister_mtd_blktrans ( struct mtd_blktrans_ops * tr )
{
struct list_head * this , * next ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
/* Clean up the kernel thread */
2007-04-22 23:40:57 +04:00
kthread_stop ( tr - > blkcore_priv - > thread ) ;
2005-04-17 02:20:36 +04:00
/* Remove it from the list of active majors */
list_del ( & tr - > list ) ;
list_for_each_safe ( this , next , & tr - > devs ) {
struct mtd_blktrans_dev * dev = list_entry ( this , struct mtd_blktrans_dev , list ) ;
tr - > remove_dev ( dev ) ;
}
blk_cleanup_queue ( tr - > blkcore_priv - > rq ) ;
unregister_blkdev ( tr - > major , tr - > name ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
kfree ( tr - > blkcore_priv ) ;
2006-03-26 20:15:12 +04:00
BUG_ON ( ! list_empty ( & tr - > devs ) ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void __exit mtd_blktrans_exit ( void )
{
/* No race here -- if someone's currently in register_mtd_blktrans
we ' re screwed anyway . */
if ( blktrans_notifier . list . next )
unregister_mtd_user ( & blktrans_notifier ) ;
}
module_exit ( mtd_blktrans_exit ) ;
EXPORT_SYMBOL_GPL ( register_mtd_blktrans ) ;
EXPORT_SYMBOL_GPL ( deregister_mtd_blktrans ) ;
EXPORT_SYMBOL_GPL ( add_mtd_blktrans_dev ) ;
EXPORT_SYMBOL_GPL ( del_mtd_blktrans_dev ) ;
MODULE_AUTHOR ( " David Woodhouse <dwmw2@infradead.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Common interface to block layer for MTD 'translation layers' " ) ;