2005-04-17 02:20:36 +04:00
/*
* Character - device access to raw MTD devices .
*
*/
2005-11-07 02:14:42 +03:00
# include <linux/device.h>
# include <linux/fs.h>
2007-08-11 00:01:06 +04:00
# include <linux/mm.h>
2006-10-11 15:52:47 +04:00
# include <linux/err.h>
2005-11-07 02:14:42 +03:00
# include <linux/init.h>
2005-04-17 02:20:36 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
2005-11-07 02:14:42 +03:00
# include <linux/slab.h>
# include <linux/sched.h>
2008-05-15 20:10:37 +04:00
# include <linux/smp_lock.h>
2009-02-12 13:40:00 +03:00
# include <linux/backing-dev.h>
2009-04-09 09:53:13 +04:00
# include <linux/compat.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/compatmac.h>
2005-11-07 02:14:42 +03:00
# include <asm/uaccess.h>
2005-06-30 04:23:27 +04:00
2005-04-17 02:20:36 +04:00
2005-02-08 22:12:53 +03:00
/*
2006-05-30 02:37:34 +04:00
* Data structure to hold the pointer to the mtd device as well
* as mode information ofr various use cases .
2005-02-08 22:12:53 +03:00
*/
2006-05-30 02:37:34 +04:00
struct mtd_file_info {
struct mtd_info * mtd ;
enum mtd_file_modes mode ;
} ;
2005-02-08 20:45:55 +03:00
2005-04-17 02:20:36 +04:00
static loff_t mtd_lseek ( struct file * file , loff_t offset , int orig )
{
2006-05-30 02:37:34 +04:00
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
2005-04-17 02:20:36 +04:00
switch ( orig ) {
2006-09-17 05:09:29 +04:00
case SEEK_SET :
2005-04-17 02:20:36 +04:00
break ;
2006-09-17 05:09:29 +04:00
case SEEK_CUR :
2005-08-04 05:05:51 +04:00
offset + = file - > f_pos ;
2005-04-17 02:20:36 +04:00
break ;
2006-09-17 05:09:29 +04:00
case SEEK_END :
2005-08-04 05:05:51 +04:00
offset + = mtd - > size ;
2005-04-17 02:20:36 +04:00
break ;
default :
return - EINVAL ;
}
[MTD] CORE mtdchar.c: fix off-by-one error in lseek()
Allow lseek(mtdchar_fd, 0, SEEK_END) to succeed, which currently fails
with EINVAL.
lseek(fd, 0, SEEK_END) should result into the same fileposition as
lseek(fd, 0, SEEK_SET) + read(fd, buf, length(fd))
Furthermore, lseek(fd, 0, SEEK_CUR) should return the current file position,
which in case of an encountered EOF should not result in EINVAL
Signed-off-by: Herbert Valerio Riedel <hvr@gnu.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
2006-06-24 02:03:36 +04:00
if ( offset > = 0 & & offset < = mtd - > size )
2005-08-04 05:05:51 +04:00
return file - > f_pos = offset ;
2005-04-17 02:20:36 +04:00
2005-08-04 05:05:51 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
static int mtd_open ( struct inode * inode , struct file * file )
{
int minor = iminor ( inode ) ;
int devnum = minor > > 1 ;
2008-05-15 20:10:37 +04:00
int ret = 0 ;
2005-04-17 02:20:36 +04:00
struct mtd_info * mtd ;
2006-05-30 02:37:34 +04:00
struct mtd_file_info * mfi ;
2005-04-17 02:20:36 +04:00
DEBUG ( MTD_DEBUG_LEVEL0 , " MTD_open \n " ) ;
if ( devnum > = MAX_MTD_DEVICES )
return - ENODEV ;
/* You can't open the RO devices RW */
2008-09-02 23:28:45 +04:00
if ( ( file - > f_mode & FMODE_WRITE ) & & ( minor & 1 ) )
2005-04-17 02:20:36 +04:00
return - EACCES ;
2008-05-15 20:10:37 +04:00
lock_kernel ( ) ;
2005-04-17 02:20:36 +04:00
mtd = get_mtd_device ( NULL , devnum ) ;
2005-11-07 14:15:26 +03:00
2008-05-15 20:10:37 +04:00
if ( IS_ERR ( mtd ) ) {
ret = PTR_ERR ( mtd ) ;
goto out ;
}
2005-11-07 14:15:26 +03:00
2009-02-12 13:40:00 +03:00
if ( mtd - > type = = MTD_ABSENT ) {
2005-04-17 02:20:36 +04:00
put_mtd_device ( mtd ) ;
2008-05-15 20:10:37 +04:00
ret = - ENODEV ;
goto out ;
2005-04-17 02:20:36 +04:00
}
2009-02-12 13:40:00 +03:00
if ( mtd - > backing_dev_info )
file - > f_mapping - > backing_dev_info = mtd - > backing_dev_info ;
2005-04-17 02:20:36 +04:00
/* You can't open it RW if it's not a writeable device */
2008-09-02 23:28:45 +04:00
if ( ( file - > f_mode & FMODE_WRITE ) & & ! ( mtd - > flags & MTD_WRITEABLE ) ) {
2005-04-17 02:20:36 +04:00
put_mtd_device ( mtd ) ;
2008-05-15 20:10:37 +04:00
ret = - EACCES ;
goto out ;
2005-04-17 02:20:36 +04:00
}
2005-11-07 14:15:26 +03:00
2006-05-30 02:37:34 +04:00
mfi = kzalloc ( sizeof ( * mfi ) , GFP_KERNEL ) ;
if ( ! mfi ) {
put_mtd_device ( mtd ) ;
2008-05-15 20:10:37 +04:00
ret = - ENOMEM ;
goto out ;
2006-05-30 02:37:34 +04:00
}
mfi - > mtd = mtd ;
file - > private_data = mfi ;
2008-05-15 20:10:37 +04:00
out :
unlock_kernel ( ) ;
return ret ;
2005-04-17 02:20:36 +04:00
} /* mtd_open */
/*====================================================================*/
static int mtd_close ( struct inode * inode , struct file * file )
{
2006-05-30 02:37:34 +04:00
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
2005-04-17 02:20:36 +04:00
DEBUG ( MTD_DEBUG_LEVEL0 , " MTD_close \n " ) ;
2007-06-27 02:56:40 +04:00
/* Only sync if opened RW */
2008-09-02 23:28:45 +04:00
if ( ( file - > f_mode & FMODE_WRITE ) & & mtd - > sync )
2005-04-17 02:20:36 +04:00
mtd - > sync ( mtd ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
put_mtd_device ( mtd ) ;
2006-05-30 02:37:34 +04:00
file - > private_data = NULL ;
kfree ( mfi ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
} /* mtd_close */
/* FIXME: This _really_ needs to die. In 2.5, we should lock the
userspace buffer down and use it directly with readv / writev .
*/
# define MAX_KMALLOC_SIZE 0x20000
static ssize_t mtd_read ( struct file * file , char __user * buf , size_t count , loff_t * ppos )
{
2006-05-30 02:37:34 +04:00
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
2005-04-17 02:20:36 +04:00
size_t retlen = 0 ;
size_t total_retlen = 0 ;
int ret = 0 ;
int len ;
char * kbuf ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
DEBUG ( MTD_DEBUG_LEVEL0 , " MTD_read \n " ) ;
if ( * ppos + count > mtd - > size )
count = mtd - > size - * ppos ;
if ( ! count )
return 0 ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
/* FIXME: Use kiovec in 2.5 to lock down the user's buffers
and pass them directly to the MTD functions */
2006-04-17 20:38:15 +04:00
if ( count > MAX_KMALLOC_SIZE )
kbuf = kmalloc ( MAX_KMALLOC_SIZE , GFP_KERNEL ) ;
else
kbuf = kmalloc ( count , GFP_KERNEL ) ;
if ( ! kbuf )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
while ( count ) {
2006-04-17 20:38:15 +04:00
2005-11-07 14:15:26 +03:00
if ( count > MAX_KMALLOC_SIZE )
2005-04-17 02:20:36 +04:00
len = MAX_KMALLOC_SIZE ;
else
len = count ;
2006-05-30 02:37:34 +04:00
switch ( mfi - > mode ) {
case MTD_MODE_OTP_FACTORY :
2005-02-08 20:45:55 +03:00
ret = mtd - > read_fact_prot_reg ( mtd , * ppos , len , & retlen , kbuf ) ;
break ;
case MTD_MODE_OTP_USER :
ret = mtd - > read_user_prot_reg ( mtd , * ppos , len , & retlen , kbuf ) ;
break ;
2006-05-30 02:37:34 +04:00
case MTD_MODE_RAW :
{
struct mtd_oob_ops ops ;
ops . mode = MTD_OOB_RAW ;
ops . datbuf = kbuf ;
ops . oobbuf = NULL ;
ops . len = len ;
ret = mtd - > read_oob ( mtd , * ppos , & ops ) ;
retlen = ops . retlen ;
break ;
}
2005-02-08 20:45:55 +03:00
default :
2006-05-28 13:01:53 +04:00
ret = mtd - > read ( mtd , * ppos , len , & retlen , kbuf ) ;
2005-02-08 20:45:55 +03:00
}
2005-04-17 02:20:36 +04:00
/* Nand returns -EBADMSG on ecc errors, but it returns
* the data . For our userspace tools it is important
2005-11-07 14:15:26 +03:00
* to dump areas with ecc errors !
2006-05-29 16:56:39 +04:00
* For kernel internal usage it also might return - EUCLEAN
* to signal the caller that a bitflip has occured and has
* been corrected by the ECC algorithm .
2005-04-17 02:20:36 +04:00
* Userspace software which accesses NAND this way
* must be aware of the fact that it deals with NAND
*/
2006-05-29 16:56:39 +04:00
if ( ! ret | | ( ret = = - EUCLEAN ) | | ( ret = = - EBADMSG ) ) {
2005-04-17 02:20:36 +04:00
* ppos + = retlen ;
if ( copy_to_user ( buf , kbuf , retlen ) ) {
2006-05-28 13:01:53 +04:00
kfree ( kbuf ) ;
2005-04-17 02:20:36 +04:00
return - EFAULT ;
}
else
total_retlen + = retlen ;
count - = retlen ;
buf + = retlen ;
2005-02-08 20:45:55 +03:00
if ( retlen = = 0 )
count = 0 ;
2005-04-17 02:20:36 +04:00
}
else {
kfree ( kbuf ) ;
return ret ;
}
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
}
2006-04-17 20:38:15 +04:00
kfree ( kbuf ) ;
2005-04-17 02:20:36 +04:00
return total_retlen ;
} /* mtd_read */
static ssize_t mtd_write ( struct file * file , const char __user * buf , size_t count , loff_t * ppos )
{
2006-05-30 02:37:34 +04:00
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
2005-04-17 02:20:36 +04:00
char * kbuf ;
size_t retlen ;
size_t total_retlen = 0 ;
int ret = 0 ;
int len ;
DEBUG ( MTD_DEBUG_LEVEL0 , " MTD_write \n " ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
if ( * ppos = = mtd - > size )
return - ENOSPC ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
if ( * ppos + count > mtd - > size )
count = mtd - > size - * ppos ;
if ( ! count )
return 0 ;
2006-04-17 20:38:15 +04:00
if ( count > MAX_KMALLOC_SIZE )
kbuf = kmalloc ( MAX_KMALLOC_SIZE , GFP_KERNEL ) ;
else
kbuf = kmalloc ( count , GFP_KERNEL ) ;
if ( ! kbuf )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
while ( count ) {
2006-04-17 20:38:15 +04:00
2005-11-07 14:15:26 +03:00
if ( count > MAX_KMALLOC_SIZE )
2005-04-17 02:20:36 +04:00
len = MAX_KMALLOC_SIZE ;
else
len = count ;
if ( copy_from_user ( kbuf , buf , len ) ) {
kfree ( kbuf ) ;
return - EFAULT ;
}
2005-11-07 14:15:26 +03:00
2006-05-30 02:37:34 +04:00
switch ( mfi - > mode ) {
case MTD_MODE_OTP_FACTORY :
2005-02-08 20:45:55 +03:00
ret = - EROFS ;
break ;
case MTD_MODE_OTP_USER :
if ( ! mtd - > write_user_prot_reg ) {
ret = - EOPNOTSUPP ;
break ;
}
ret = mtd - > write_user_prot_reg ( mtd , * ppos , len , & retlen , kbuf ) ;
break ;
2006-05-30 02:37:34 +04:00
case MTD_MODE_RAW :
{
struct mtd_oob_ops ops ;
ops . mode = MTD_OOB_RAW ;
ops . datbuf = kbuf ;
ops . oobbuf = NULL ;
ops . len = len ;
ret = mtd - > write_oob ( mtd , * ppos , & ops ) ;
retlen = ops . retlen ;
break ;
}
2005-02-08 20:45:55 +03:00
default :
ret = ( * ( mtd - > write ) ) ( mtd , * ppos , len , & retlen , kbuf ) ;
}
2005-04-17 02:20:36 +04:00
if ( ! ret ) {
* ppos + = retlen ;
total_retlen + = retlen ;
count - = retlen ;
buf + = retlen ;
}
else {
kfree ( kbuf ) ;
return ret ;
}
}
2006-04-17 20:38:15 +04:00
kfree ( kbuf ) ;
2005-04-17 02:20:36 +04:00
return total_retlen ;
} /* mtd_write */
/*======================================================================
IOCTL calls for getting device parameters .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
static void mtdchar_erase_callback ( struct erase_info * instr )
{
wake_up ( ( wait_queue_head_t * ) instr - > priv ) ;
}
2008-07-30 23:35:05 +04:00
# ifdef CONFIG_HAVE_MTD_OTP
2006-05-30 02:37:34 +04:00
static int otp_select_filemode ( struct mtd_file_info * mfi , int mode )
{
struct mtd_info * mtd = mfi - > mtd ;
int ret = 0 ;
switch ( mode ) {
case MTD_OTP_FACTORY :
if ( ! mtd - > read_fact_prot_reg )
ret = - EOPNOTSUPP ;
else
mfi - > mode = MTD_MODE_OTP_FACTORY ;
break ;
case MTD_OTP_USER :
if ( ! mtd - > read_fact_prot_reg )
ret = - EOPNOTSUPP ;
else
mfi - > mode = MTD_MODE_OTP_USER ;
break ;
default :
ret = - EINVAL ;
case MTD_OTP_OFF :
break ;
}
return ret ;
}
# else
# define otp_select_filemode(f,m) -EOPNOTSUPP
# endif
2009-04-09 09:53:13 +04:00
static int mtd_do_writeoob ( struct file * file , struct mtd_info * mtd ,
uint64_t start , uint32_t length , void __user * ptr ,
uint32_t __user * retp )
{
struct mtd_oob_ops ops ;
uint32_t retlen ;
int ret = 0 ;
if ( ! ( file - > f_mode & FMODE_WRITE ) )
return - EPERM ;
if ( length > 4096 )
return - EINVAL ;
if ( ! mtd - > write_oob )
ret = - EOPNOTSUPP ;
else
ret = access_ok ( VERIFY_READ , ptr , length ) ? 0 : EFAULT ;
if ( ret )
return ret ;
ops . ooblen = length ;
ops . ooboffs = start & ( mtd - > oobsize - 1 ) ;
ops . datbuf = NULL ;
ops . mode = MTD_OOB_PLACE ;
if ( ops . ooboffs & & ops . ooblen > ( mtd - > oobsize - ops . ooboffs ) )
return - EINVAL ;
ops . oobbuf = kmalloc ( length , GFP_KERNEL ) ;
if ( ! ops . oobbuf )
return - ENOMEM ;
if ( copy_from_user ( ops . oobbuf , ptr , length ) ) {
kfree ( ops . oobbuf ) ;
return - EFAULT ;
}
start & = ~ ( ( uint64_t ) mtd - > oobsize - 1 ) ;
ret = mtd - > write_oob ( mtd , start , & ops ) ;
if ( ops . oobretlen > 0xFFFFFFFFU )
ret = - EOVERFLOW ;
retlen = ops . oobretlen ;
if ( copy_to_user ( retp , & retlen , sizeof ( length ) ) )
ret = - EFAULT ;
kfree ( ops . oobbuf ) ;
return ret ;
}
static int mtd_do_readoob ( struct mtd_info * mtd , uint64_t start ,
uint32_t length , void __user * ptr , uint32_t __user * retp )
{
struct mtd_oob_ops ops ;
int ret = 0 ;
if ( length > 4096 )
return - EINVAL ;
if ( ! mtd - > read_oob )
ret = - EOPNOTSUPP ;
else
ret = access_ok ( VERIFY_WRITE , ptr ,
length ) ? 0 : - EFAULT ;
if ( ret )
return ret ;
ops . ooblen = length ;
ops . ooboffs = start & ( mtd - > oobsize - 1 ) ;
ops . datbuf = NULL ;
ops . mode = MTD_OOB_PLACE ;
if ( ops . ooboffs & & ops . ooblen > ( mtd - > oobsize - ops . ooboffs ) )
return - EINVAL ;
ops . oobbuf = kmalloc ( length , GFP_KERNEL ) ;
if ( ! ops . oobbuf )
return - ENOMEM ;
start & = ~ ( ( uint64_t ) mtd - > oobsize - 1 ) ;
ret = mtd - > read_oob ( mtd , start , & ops ) ;
if ( put_user ( ops . oobretlen , retp ) )
ret = - EFAULT ;
else if ( ops . oobretlen & & copy_to_user ( ptr , ops . oobbuf ,
ops . oobretlen ) )
ret = - EFAULT ;
kfree ( ops . oobbuf ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
static int mtd_ioctl ( struct inode * inode , struct file * file ,
u_int cmd , u_long arg )
{
2006-05-30 02:37:34 +04:00
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
2005-04-17 02:20:36 +04:00
void __user * argp = ( void __user * ) arg ;
int ret = 0 ;
u_long size ;
2006-05-30 16:25:35 +04:00
struct mtd_info_user info ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
DEBUG ( MTD_DEBUG_LEVEL0 , " MTD_ioctl \n " ) ;
size = ( cmd & IOCSIZE_MASK ) > > IOCSIZE_SHIFT ;
if ( cmd & IOC_IN ) {
if ( ! access_ok ( VERIFY_READ , argp , size ) )
return - EFAULT ;
}
if ( cmd & IOC_OUT ) {
if ( ! access_ok ( VERIFY_WRITE , argp , size ) )
return - EFAULT ;
}
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
switch ( cmd ) {
case MEMGETREGIONCOUNT :
if ( copy_to_user ( argp , & ( mtd - > numeraseregions ) , sizeof ( int ) ) )
return - EFAULT ;
break ;
case MEMGETREGIONINFO :
{
2008-09-01 16:02:12 +04:00
uint32_t ur_idx ;
struct mtd_erase_region_info * kr ;
struct region_info_user * ur = ( struct region_info_user * ) argp ;
2005-04-17 02:20:36 +04:00
2008-09-01 16:02:12 +04:00
if ( get_user ( ur_idx , & ( ur - > regionindex ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
2008-09-01 16:02:12 +04:00
kr = & ( mtd - > eraseregions [ ur_idx ] ) ;
if ( put_user ( kr - > offset , & ( ur - > offset ) )
| | put_user ( kr - > erasesize , & ( ur - > erasesize ) )
| | put_user ( kr - > numblocks , & ( ur - > numblocks ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
2008-09-01 16:02:12 +04:00
2005-04-17 02:20:36 +04:00
break ;
}
case MEMGETINFO :
2006-05-30 16:25:35 +04:00
info . type = mtd - > type ;
info . flags = mtd - > flags ;
info . size = mtd - > size ;
info . erasesize = mtd - > erasesize ;
info . writesize = mtd - > writesize ;
info . oobsize = mtd - > oobsize ;
2007-01-30 11:50:43 +03:00
/* The below fields are obsolete */
info . ecctype = - 1 ;
info . eccsize = 0 ;
2006-05-30 16:25:35 +04:00
if ( copy_to_user ( argp , & info , sizeof ( struct mtd_info_user ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
break ;
case MEMERASE :
2009-04-09 09:52:28 +04:00
case MEMERASE64 :
2005-04-17 02:20:36 +04:00
{
struct erase_info * erase ;
2008-09-02 23:28:45 +04:00
if ( ! ( file - > f_mode & FMODE_WRITE ) )
2005-04-17 02:20:36 +04:00
return - EPERM ;
2006-11-15 22:10:29 +03:00
erase = kzalloc ( sizeof ( struct erase_info ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! erase )
ret = - ENOMEM ;
else {
wait_queue_head_t waitq ;
DECLARE_WAITQUEUE ( wait , current ) ;
init_waitqueue_head ( & waitq ) ;
2009-04-09 09:52:28 +04:00
if ( cmd = = MEMERASE64 ) {
struct erase_info_user64 einfo64 ;
if ( copy_from_user ( & einfo64 , argp ,
sizeof ( struct erase_info_user64 ) ) ) {
kfree ( erase ) ;
return - EFAULT ;
}
erase - > addr = einfo64 . start ;
erase - > len = einfo64 . length ;
} else {
struct erase_info_user einfo32 ;
if ( copy_from_user ( & einfo32 , argp ,
sizeof ( struct erase_info_user ) ) ) {
kfree ( erase ) ;
return - EFAULT ;
}
erase - > addr = einfo32 . start ;
erase - > len = einfo32 . length ;
2005-04-17 02:20:36 +04:00
}
erase - > mtd = mtd ;
erase - > callback = mtdchar_erase_callback ;
erase - > priv = ( unsigned long ) & waitq ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
/*
FIXME : Allow INTERRUPTIBLE . Which means
not having the wait_queue head on the stack .
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
If the wq_head is on the stack , and we
leave because we got interrupted , then the
wq_head is no longer there when the
callback routine tries to wake us up .
*/
ret = mtd - > erase ( mtd , erase ) ;
if ( ! ret ) {
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
add_wait_queue ( & waitq , & wait ) ;
if ( erase - > state ! = MTD_ERASE_DONE & &
erase - > state ! = MTD_ERASE_FAILED )
schedule ( ) ;
remove_wait_queue ( & waitq , & wait ) ;
set_current_state ( TASK_RUNNING ) ;
ret = ( erase - > state = = MTD_ERASE_FAILED ) ? - EIO : 0 ;
}
kfree ( erase ) ;
}
break ;
}
case MEMWRITEOOB :
{
struct mtd_oob_buf buf ;
2009-04-09 09:53:13 +04:00
struct mtd_oob_buf __user * buf_user = argp ;
2005-04-17 02:20:36 +04:00
2009-04-09 09:53:13 +04:00
/* NOTE: writes return length to buf_user->length */
if ( copy_from_user ( & buf , argp , sizeof ( buf ) ) )
2005-04-17 02:20:36 +04:00
ret = - EFAULT ;
2009-04-09 09:53:13 +04:00
else
ret = mtd_do_writeoob ( file , mtd , buf . start , buf . length ,
buf . ptr , & buf_user - > length ) ;
2005-04-17 02:20:36 +04:00
break ;
}
case MEMREADOOB :
{
struct mtd_oob_buf buf ;
2009-04-09 09:53:13 +04:00
struct mtd_oob_buf __user * buf_user = argp ;
2006-05-29 05:26:58 +04:00
2009-04-09 09:53:13 +04:00
/* NOTE: writes return length to buf_user->start */
if ( copy_from_user ( & buf , argp , sizeof ( buf ) ) )
2005-04-17 02:20:36 +04:00
ret = - EFAULT ;
2009-04-09 09:53:13 +04:00
else
ret = mtd_do_readoob ( mtd , buf . start , buf . length ,
buf . ptr , & buf_user - > start ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2009-04-09 09:53:49 +04:00
case MEMWRITEOOB64 :
{
struct mtd_oob_buf64 buf ;
struct mtd_oob_buf64 __user * buf_user = argp ;
if ( copy_from_user ( & buf , argp , sizeof ( buf ) ) )
ret = - EFAULT ;
else
ret = mtd_do_writeoob ( file , mtd , buf . start , buf . length ,
( void __user * ) ( uintptr_t ) buf . usr_ptr ,
& buf_user - > length ) ;
break ;
}
case MEMREADOOB64 :
{
struct mtd_oob_buf64 buf ;
struct mtd_oob_buf64 __user * buf_user = argp ;
if ( copy_from_user ( & buf , argp , sizeof ( buf ) ) )
ret = - EFAULT ;
else
ret = mtd_do_readoob ( mtd , buf . start , buf . length ,
( void __user * ) ( uintptr_t ) buf . usr_ptr ,
& buf_user - > length ) ;
break ;
}
2005-04-17 02:20:36 +04:00
case MEMLOCK :
{
2008-07-04 10:40:14 +04:00
struct erase_info_user einfo ;
2005-04-17 02:20:36 +04:00
2008-07-04 10:40:14 +04:00
if ( copy_from_user ( & einfo , argp , sizeof ( einfo ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
if ( ! mtd - > lock )
ret = - EOPNOTSUPP ;
else
2008-07-04 10:40:14 +04:00
ret = mtd - > lock ( mtd , einfo . start , einfo . length ) ;
2005-04-17 02:20:36 +04:00
break ;
}
case MEMUNLOCK :
{
2008-07-04 10:40:14 +04:00
struct erase_info_user einfo ;
2005-04-17 02:20:36 +04:00
2008-07-04 10:40:14 +04:00
if ( copy_from_user ( & einfo , argp , sizeof ( einfo ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
if ( ! mtd - > unlock )
ret = - EOPNOTSUPP ;
else
2008-07-04 10:40:14 +04:00
ret = mtd - > unlock ( mtd , einfo . start , einfo . length ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2006-05-28 00:16:10 +04:00
/* Legacy interface */
2005-04-17 02:20:36 +04:00
case MEMGETOOBSEL :
{
2006-05-28 00:16:10 +04:00
struct nand_oobinfo oi ;
if ( ! mtd - > ecclayout )
return - EOPNOTSUPP ;
if ( mtd - > ecclayout - > eccbytes > ARRAY_SIZE ( oi . eccpos ) )
return - EINVAL ;
oi . useecc = MTD_NANDECC_AUTOPLACE ;
memcpy ( & oi . eccpos , mtd - > ecclayout - > eccpos , sizeof ( oi . eccpos ) ) ;
memcpy ( & oi . oobfree , mtd - > ecclayout - > oobfree ,
sizeof ( oi . oobfree ) ) ;
2006-10-17 19:27:11 +04:00
oi . eccbytes = mtd - > ecclayout - > eccbytes ;
2006-05-28 00:16:10 +04:00
if ( copy_to_user ( argp , & oi , sizeof ( struct nand_oobinfo ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
break ;
}
case MEMGETBADBLOCK :
{
loff_t offs ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
if ( copy_from_user ( & offs , argp , sizeof ( loff_t ) ) )
return - EFAULT ;
if ( ! mtd - > block_isbad )
ret = - EOPNOTSUPP ;
else
return mtd - > block_isbad ( mtd , offs ) ;
break ;
}
case MEMSETBADBLOCK :
{
loff_t offs ;
if ( copy_from_user ( & offs , argp , sizeof ( loff_t ) ) )
return - EFAULT ;
if ( ! mtd - > block_markbad )
ret = - EOPNOTSUPP ;
else
return mtd - > block_markbad ( mtd , offs ) ;
break ;
}
2008-07-30 23:35:05 +04:00
# ifdef CONFIG_HAVE_MTD_OTP
2005-02-08 20:45:55 +03:00
case OTPSELECT :
{
int mode ;
if ( copy_from_user ( & mode , argp , sizeof ( int ) ) )
return - EFAULT ;
2006-05-30 02:37:34 +04:00
mfi - > mode = MTD_MODE_NORMAL ;
ret = otp_select_filemode ( mfi , mode ) ;
2005-04-01 19:36:15 +04:00
file - > f_pos = 0 ;
2005-02-08 20:45:55 +03:00
break ;
}
case OTPGETREGIONCOUNT :
case OTPGETREGIONINFO :
{
struct otp_info * buf = kmalloc ( 4096 , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
ret = - EOPNOTSUPP ;
2006-05-30 02:37:34 +04:00
switch ( mfi - > mode ) {
case MTD_MODE_OTP_FACTORY :
2005-02-08 20:45:55 +03:00
if ( mtd - > get_fact_prot_info )
ret = mtd - > get_fact_prot_info ( mtd , buf , 4096 ) ;
break ;
case MTD_MODE_OTP_USER :
if ( mtd - > get_user_prot_info )
ret = mtd - > get_user_prot_info ( mtd , buf , 4096 ) ;
break ;
2006-05-30 02:37:34 +04:00
default :
break ;
2005-02-08 20:45:55 +03:00
}
if ( ret > = 0 ) {
if ( cmd = = OTPGETREGIONCOUNT ) {
int nbr = ret / sizeof ( struct otp_info ) ;
ret = copy_to_user ( argp , & nbr , sizeof ( int ) ) ;
} else
ret = copy_to_user ( argp , buf , ret ) ;
if ( ret )
ret = - EFAULT ;
}
kfree ( buf ) ;
break ;
}
case OTPLOCK :
{
2008-07-04 10:40:14 +04:00
struct otp_info oinfo ;
2005-02-08 20:45:55 +03:00
2006-05-30 02:37:34 +04:00
if ( mfi - > mode ! = MTD_MODE_OTP_USER )
2005-02-08 20:45:55 +03:00
return - EINVAL ;
2008-07-04 10:40:14 +04:00
if ( copy_from_user ( & oinfo , argp , sizeof ( oinfo ) ) )
2005-02-08 20:45:55 +03:00
return - EFAULT ;
if ( ! mtd - > lock_user_prot_reg )
return - EOPNOTSUPP ;
2008-07-04 10:40:14 +04:00
ret = mtd - > lock_user_prot_reg ( mtd , oinfo . start , oinfo . length ) ;
2005-02-08 20:45:55 +03:00
break ;
}
# endif
2006-05-30 02:37:34 +04:00
case ECCGETLAYOUT :
{
if ( ! mtd - > ecclayout )
return - EOPNOTSUPP ;
2006-10-17 19:27:11 +04:00
if ( copy_to_user ( argp , mtd - > ecclayout ,
2006-05-30 02:37:34 +04:00
sizeof ( struct nand_ecclayout ) ) )
return - EFAULT ;
break ;
}
case ECCGETSTATS :
{
if ( copy_to_user ( argp , & mtd - > ecc_stats ,
sizeof ( struct mtd_ecc_stats ) ) )
return - EFAULT ;
break ;
}
case MTDFILEMODE :
{
mfi - > mode = 0 ;
switch ( arg ) {
case MTD_MODE_OTP_FACTORY :
case MTD_MODE_OTP_USER :
ret = otp_select_filemode ( mfi , arg ) ;
break ;
case MTD_MODE_RAW :
if ( ! mtd - > read_oob | | ! mtd - > write_oob )
return - EOPNOTSUPP ;
mfi - > mode = arg ;
case MTD_MODE_NORMAL :
break ;
default :
ret = - EINVAL ;
}
file - > f_pos = 0 ;
break ;
}
2005-04-17 02:20:36 +04:00
default :
ret = - ENOTTY ;
}
return ret ;
} /* memory_ioctl */
2009-04-09 09:53:13 +04:00
# ifdef CONFIG_COMPAT
struct mtd_oob_buf32 {
u_int32_t start ;
u_int32_t length ;
compat_caddr_t ptr ; /* unsigned char* */
} ;
# define MEMWRITEOOB32 _IOWR('M', 3, struct mtd_oob_buf32)
# define MEMREADOOB32 _IOWR('M', 4, struct mtd_oob_buf32)
static long mtd_compat_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
2009-04-15 08:59:22 +04:00
struct inode * inode = file - > f_path . dentry - > d_inode ;
2009-04-09 09:53:13 +04:00
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
2009-05-29 19:09:08 +04:00
void __user * argp = compat_ptr ( arg ) ;
2009-04-09 09:53:13 +04:00
int ret = 0 ;
lock_kernel ( ) ;
switch ( cmd ) {
case MEMWRITEOOB32 :
{
struct mtd_oob_buf32 buf ;
struct mtd_oob_buf32 __user * buf_user = argp ;
if ( copy_from_user ( & buf , argp , sizeof ( buf ) ) )
ret = - EFAULT ;
else
ret = mtd_do_writeoob ( file , mtd , buf . start ,
buf . length , compat_ptr ( buf . ptr ) ,
& buf_user - > length ) ;
break ;
}
case MEMREADOOB32 :
{
struct mtd_oob_buf32 buf ;
struct mtd_oob_buf32 __user * buf_user = argp ;
/* NOTE: writes return length to buf->start */
if ( copy_from_user ( & buf , argp , sizeof ( buf ) ) )
ret = - EFAULT ;
else
ret = mtd_do_readoob ( mtd , buf . start ,
buf . length , compat_ptr ( buf . ptr ) ,
& buf_user - > start ) ;
break ;
}
default :
2009-05-29 19:09:08 +04:00
ret = mtd_ioctl ( inode , file , cmd , ( unsigned long ) argp ) ;
2009-04-09 09:53:13 +04:00
}
unlock_kernel ( ) ;
return ret ;
}
# endif /* CONFIG_COMPAT */
2009-02-12 13:40:00 +03:00
/*
* try to determine where a shared mapping can be made
* - only supported for NOMMU at the moment ( MMU can ' t doesn ' t copy private
* mappings )
*/
# ifndef CONFIG_MMU
static unsigned long mtd_get_unmapped_area ( struct file * file ,
unsigned long addr ,
unsigned long len ,
unsigned long pgoff ,
unsigned long flags )
{
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
if ( mtd - > get_unmapped_area ) {
unsigned long offset ;
if ( addr ! = 0 )
return ( unsigned long ) - EINVAL ;
if ( len > mtd - > size | | pgoff > = ( mtd - > size > > PAGE_SHIFT ) )
return ( unsigned long ) - EINVAL ;
offset = pgoff < < PAGE_SHIFT ;
if ( offset > mtd - > size - len )
return ( unsigned long ) - EINVAL ;
return mtd - > get_unmapped_area ( mtd , len , offset , flags ) ;
}
/* can't map directly */
return ( unsigned long ) - ENOSYS ;
}
# endif
/*
* set up a mapping for shared memory segments
*/
static int mtd_mmap ( struct file * file , struct vm_area_struct * vma )
{
# ifdef CONFIG_MMU
struct mtd_file_info * mfi = file - > private_data ;
struct mtd_info * mtd = mfi - > mtd ;
if ( mtd - > type = = MTD_RAM | | mtd - > type = = MTD_ROM )
return 0 ;
return - ENOSYS ;
# else
return vma - > vm_flags & VM_SHARED ? 0 : - ENOSYS ;
# endif
}
2007-02-12 11:55:34 +03:00
static const struct file_operations mtd_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = mtd_lseek ,
. read = mtd_read ,
. write = mtd_write ,
. ioctl = mtd_ioctl ,
2009-04-09 09:53:13 +04:00
# ifdef CONFIG_COMPAT
. compat_ioctl = mtd_compat_ioctl ,
# endif
2005-04-17 02:20:36 +04:00
. open = mtd_open ,
. release = mtd_close ,
2009-02-12 13:40:00 +03:00
. mmap = mtd_mmap ,
# ifndef CONFIG_MMU
. get_unmapped_area = mtd_get_unmapped_area ,
# endif
2005-04-17 02:20:36 +04:00
} ;
static int __init init_mtdchar ( void )
{
2009-03-26 10:42:41 +03:00
int status ;
status = register_chrdev ( MTD_CHAR_MAJOR , " mtd " , & mtd_fops ) ;
if ( status < 0 ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_NOTICE " Can't allocate major number %d for Memory Technology Devices. \n " ,
MTD_CHAR_MAJOR ) ;
2005-06-30 04:23:27 +04:00
}
2009-03-26 10:42:41 +03:00
return status ;
2005-04-17 02:20:36 +04:00
}
static void __exit cleanup_mtdchar ( void )
{
unregister_chrdev ( MTD_CHAR_MAJOR , " mtd " ) ;
}
module_init ( init_mtdchar ) ;
module_exit ( cleanup_mtdchar ) ;
2009-03-26 10:42:41 +03:00
MODULE_ALIAS_CHARDEV_MAJOR ( MTD_CHAR_MAJOR ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " David Woodhouse <dwmw2@infradead.org> " ) ;
MODULE_DESCRIPTION ( " Direct character-device access to MTD devices " ) ;
2009-03-02 21:42:39 +03:00
MODULE_ALIAS_CHARDEV_MAJOR ( MTD_CHAR_MAJOR ) ;