2005-04-17 02:20:36 +04:00
/*
2005-11-07 14:15:26 +03:00
* $ Id : mtdcore . c , v 1.47 2005 / 11 / 07 11 : 14 : 20 gleixner Exp $
2005-04-17 02:20:36 +04:00
*
* Core registration and callback routines for MTD
* drivers and users .
*
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/ptrace.h>
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/timer.h>
# include <linux/major.h>
# include <linux/fs.h>
# include <linux/ioctl.h>
# include <linux/init.h>
# include <linux/mtd/compatmac.h>
# include <linux/proc_fs.h>
# include <linux/mtd/mtd.h>
2005-11-07 14:15:26 +03:00
/* These are exported solely for the purpose of mtd_blkdevs.c. You
2005-04-17 02:20:36 +04:00
should not use them for _anything_ else */
2006-03-31 14:29:41 +04:00
DEFINE_MUTEX ( mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
struct mtd_info * mtd_table [ MAX_MTD_DEVICES ] ;
EXPORT_SYMBOL_GPL ( mtd_table_mutex ) ;
EXPORT_SYMBOL_GPL ( mtd_table ) ;
static LIST_HEAD ( mtd_notifiers ) ;
/**
* add_mtd_device - register an MTD device
* @ mtd : pointer to new MTD device info structure
*
* Add a device to the list of MTD devices present in the system , and
* notify each currently active MTD ' user ' of its arrival . Returns
* zero on success or 1 on failure , which currently will only happen
* if the number of present devices exceeds MAX_MTD_DEVICES ( i . e . 16 )
*/
int add_mtd_device ( struct mtd_info * mtd )
{
int i ;
2006-06-14 19:53:44 +04:00
BUG_ON ( mtd - > writesize = = 0 ) ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < MAX_MTD_DEVICES ; i + + )
if ( ! mtd_table [ i ] ) {
struct list_head * this ;
mtd_table [ i ] = mtd ;
mtd - > index = i ;
mtd - > usecount = 0 ;
2006-09-22 13:07:08 +04:00
/* Some chips always power up locked. Unlock them now */
if ( ( mtd - > flags & MTD_WRITEABLE )
& & ( mtd - > flags & MTD_STUPID_LOCK ) & & mtd - > unlock ) {
if ( mtd - > unlock ( mtd , 0 , mtd - > size ) )
printk ( KERN_WARNING
" %s: unlock failed, "
" writes may not work \n " ,
mtd - > name ) ;
}
2005-04-17 02:20:36 +04:00
DEBUG ( 0 , " mtd: Giving out device %d to %s \n " , i , mtd - > name ) ;
/* No need to get a refcount on the module containing
the notifier , since we hold the mtd_table_mutex */
list_for_each ( this , & mtd_notifiers ) {
struct mtd_notifier * not = list_entry ( this , struct mtd_notifier , list ) ;
not - > add ( mtd ) ;
}
2005-11-07 14:15:26 +03:00
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
/* We _know_ we aren't being removed, because
our caller is still holding us here . So none
of this try_ nonsense , and no bitching about it
either . : ) */
__module_get ( THIS_MODULE ) ;
return 0 ;
}
2005-11-07 14:15:26 +03:00
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return 1 ;
}
/**
* del_mtd_device - unregister an MTD device
* @ mtd : pointer to MTD device info structure
*
* Remove a device from the list of MTD devices present in the system ,
* and notify each currently active MTD ' user ' of its departure .
* Returns zero on success or 1 on failure , which currently will happen
* if the requested device does not appear to be present in the list .
*/
int del_mtd_device ( struct mtd_info * mtd )
{
int ret ;
2005-11-07 14:15:26 +03:00
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( mtd_table [ mtd - > index ] ! = mtd ) {
ret = - ENODEV ;
} else if ( mtd - > usecount ) {
2005-11-07 14:15:26 +03:00
printk ( KERN_NOTICE " Removing MTD device #%d (%s) with use count %d \n " ,
2005-04-17 02:20:36 +04:00
mtd - > index , mtd - > name , mtd - > usecount ) ;
ret = - EBUSY ;
} else {
struct list_head * this ;
/* No need to get a refcount on the module containing
the notifier , since we hold the mtd_table_mutex */
list_for_each ( this , & mtd_notifiers ) {
struct mtd_notifier * not = list_entry ( this , struct mtd_notifier , list ) ;
not - > remove ( mtd ) ;
}
mtd_table [ mtd - > index ] = NULL ;
module_put ( THIS_MODULE ) ;
ret = 0 ;
}
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
/**
* register_mtd_user - register a ' user ' of MTD devices .
* @ new : pointer to notifier info structure
*
* Registers a pair of callbacks function to be called upon addition
* or removal of MTD devices . Causes the ' add ' callback to be immediately
* invoked for each MTD device currently present in the system .
*/
void register_mtd_user ( struct mtd_notifier * new )
{
int i ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
list_add ( & new - > list , & mtd_notifiers ) ;
__module_get ( THIS_MODULE ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < MAX_MTD_DEVICES ; i + + )
if ( mtd_table [ i ] )
new - > add ( mtd_table [ i ] ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
}
/**
2005-02-18 17:34:54 +03:00
* unregister_mtd_user - unregister a ' user ' of MTD devices .
* @ old : pointer to notifier info structure
2005-04-17 02:20:36 +04:00
*
* Removes a callback function pair from the list of ' users ' to be
* notified upon addition or removal of MTD devices . Causes the
* ' remove ' callback to be immediately invoked for each MTD device
* currently present in the system .
*/
int unregister_mtd_user ( struct mtd_notifier * old )
{
int i ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
module_put ( THIS_MODULE ) ;
for ( i = 0 ; i < MAX_MTD_DEVICES ; i + + )
if ( mtd_table [ i ] )
old - > remove ( mtd_table [ i ] ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
list_del ( & old - > list ) ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
* get_mtd_device - obtain a validated handle for an MTD device
* @ mtd : last known address of the required MTD device
* @ num : internal device number of the required MTD device
*
* Given a number and NULL address , return the num ' th entry in the device
* table , if any . Given an address and num = = - 1 , search the device table
* for a device with that address and return if it ' s still present . Given
* both , return the num ' th driver only if its address matches . Return NULL
* if not .
*/
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
struct mtd_info * get_mtd_device ( struct mtd_info * mtd , int num )
{
struct mtd_info * ret = NULL ;
int i ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( num = = - 1 ) {
for ( i = 0 ; i < MAX_MTD_DEVICES ; i + + )
if ( mtd_table [ i ] = = mtd )
ret = mtd_table [ i ] ;
} else if ( num < MAX_MTD_DEVICES ) {
ret = mtd_table [ num ] ;
if ( mtd & & mtd ! = ret )
ret = NULL ;
}
if ( ret & & ! try_module_get ( ret - > owner ) )
ret = NULL ;
if ( ret )
ret - > usecount + + ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
void put_mtd_device ( struct mtd_info * mtd )
{
int c ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
c = - - mtd - > usecount ;
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
BUG_ON ( c < 0 ) ;
module_put ( mtd - > owner ) ;
}
/* default_mtd_writev - default mtd writev method for MTD devices that
* dont implement their own
*/
int default_mtd_writev ( struct mtd_info * mtd , const struct kvec * vecs ,
unsigned long count , loff_t to , size_t * retlen )
{
unsigned long i ;
size_t totlen = 0 , thislen ;
int ret = 0 ;
if ( ! mtd - > write ) {
ret = - EROFS ;
} else {
for ( i = 0 ; i < count ; i + + ) {
if ( ! vecs [ i ] . iov_len )
continue ;
ret = mtd - > write ( mtd , to , vecs [ i ] . iov_len , & thislen , vecs [ i ] . iov_base ) ;
totlen + = thislen ;
if ( ret | | thislen ! = vecs [ i ] . iov_len )
break ;
to + = vecs [ i ] . iov_len ;
}
}
if ( retlen )
* retlen = totlen ;
return ret ;
}
EXPORT_SYMBOL ( add_mtd_device ) ;
EXPORT_SYMBOL ( del_mtd_device ) ;
EXPORT_SYMBOL ( get_mtd_device ) ;
EXPORT_SYMBOL ( put_mtd_device ) ;
EXPORT_SYMBOL ( register_mtd_user ) ;
EXPORT_SYMBOL ( unregister_mtd_user ) ;
EXPORT_SYMBOL ( default_mtd_writev ) ;
2006-03-31 14:29:49 +04:00
# ifdef CONFIG_PROC_FS
2005-04-17 02:20:36 +04:00
/*====================================================================*/
/* Support for /proc/mtd */
static struct proc_dir_entry * proc_mtd ;
static inline int mtd_proc_info ( char * buf , int i )
{
struct mtd_info * this = mtd_table [ i ] ;
if ( ! this )
return 0 ;
return sprintf ( buf , " mtd%d: %8.8x %8.8x \" %s \" \n " , i , this - > size ,
this - > erasesize , this - > name ) ;
}
static int mtd_read_proc ( char * page , char * * start , off_t off , int count ,
int * eof , void * data_unused )
{
int len , l , i ;
off_t begin = 0 ;
2006-03-31 14:29:41 +04:00
mutex_lock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
len = sprintf ( page , " dev: size erasesize name \n " ) ;
for ( i = 0 ; i < MAX_MTD_DEVICES ; i + + ) {
l = mtd_proc_info ( page + len , i ) ;
len + = l ;
if ( len + begin > off + count )
goto done ;
if ( len + begin < off ) {
begin + = len ;
len = 0 ;
}
}
* eof = 1 ;
done :
2006-03-31 14:29:41 +04:00
mutex_unlock ( & mtd_table_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( off > = len + begin )
return 0 ;
* start = page + ( off - begin ) ;
return ( ( count < begin + len - off ) ? count : begin + len - off ) ;
}
/*====================================================================*/
/* Init code */
static int __init init_mtd ( void )
{
if ( ( proc_mtd = create_proc_entry ( " mtd " , 0 , NULL ) ) )
proc_mtd - > read_proc = mtd_read_proc ;
return 0 ;
}
static void __exit cleanup_mtd ( void )
{
if ( proc_mtd )
remove_proc_entry ( " mtd " , NULL ) ;
}
module_init ( init_mtd ) ;
module_exit ( cleanup_mtd ) ;
2006-03-31 14:29:49 +04:00
# endif /* CONFIG_PROC_FS */
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " David Woodhouse <dwmw2@infradead.org> " ) ;
MODULE_DESCRIPTION ( " Core MTD registration and access routines " ) ;