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>
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
static struct class * mtd_class ;
2005-04-17 02:20:36 +04:00
static void mtd_notify_add ( struct mtd_info * mtd )
{
if ( ! mtd )
return ;
2008-07-22 07:03:34 +04:00
device_create ( mtd_class , NULL , MKDEV ( MTD_CHAR_MAJOR , mtd - > index * 2 ) ,
NULL , " mtd%d " , mtd - > index ) ;
2005-11-07 14:15:26 +03:00
2008-07-22 07:03:34 +04:00
device_create ( mtd_class , NULL , MKDEV ( MTD_CHAR_MAJOR , mtd - > index * 2 + 1 ) ,
NULL , " mtd%dro " , mtd - > index ) ;
2005-04-17 02:20:36 +04:00
}
static void mtd_notify_remove ( struct mtd_info * mtd )
{
if ( ! mtd )
return ;
2005-06-30 04:23:27 +04:00
2007-09-25 04:03:03 +04:00
device_destroy ( mtd_class , MKDEV ( MTD_CHAR_MAJOR , mtd - > index * 2 ) ) ;
device_destroy ( mtd_class , MKDEV ( MTD_CHAR_MAJOR , mtd - > index * 2 + 1 ) ) ;
2005-04-17 02:20:36 +04:00
}
static struct mtd_notifier notifier = {
. add = mtd_notify_add ,
. remove = mtd_notify_remove ,
} ;
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
2005-04-17 02:20:36 +04:00
if ( MTD_ABSENT = = mtd - > type ) {
put_mtd_device ( mtd ) ;
2008-05-15 20:10:37 +04:00
ret = - ENODEV ;
goto out ;
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
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 :
{
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 {
2008-12-10 16:37:21 +03:00
struct erase_info_user einfo ;
2005-04-17 02:20:36 +04:00
wait_queue_head_t waitq ;
DECLARE_WAITQUEUE ( wait , current ) ;
init_waitqueue_head ( & waitq ) ;
2008-12-10 16:37:21 +03:00
if ( copy_from_user ( & einfo , argp ,
2005-04-17 02:20:36 +04:00
sizeof ( struct erase_info_user ) ) ) {
kfree ( erase ) ;
return - EFAULT ;
}
2008-12-10 16:37:21 +03:00
erase - > addr = einfo . start ;
erase - > len = einfo . 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 ;
2006-05-29 05:26:58 +04:00
struct mtd_oob_ops ops ;
2008-07-04 10:40:13 +04:00
struct mtd_oob_buf __user * user_buf = argp ;
2007-12-12 02:44:30 +03:00
uint32_t retlen ;
2005-11-07 14:15:26 +03:00
2008-09-02 23:28:45 +04:00
if ( ! ( file - > f_mode & FMODE_WRITE ) )
2005-04-17 02:20:36 +04:00
return - EPERM ;
if ( copy_from_user ( & buf , argp , sizeof ( struct mtd_oob_buf ) ) )
return - EFAULT ;
2005-11-07 14:15:26 +03:00
2006-05-29 05:26:58 +04:00
if ( buf . length > 4096 )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
if ( ! mtd - > write_oob )
ret = - EOPNOTSUPP ;
else
ret = access_ok ( VERIFY_READ , buf . ptr ,
buf . length ) ? 0 : EFAULT ;
if ( ret )
return ret ;
2006-06-20 22:05:05 +04:00
ops . ooblen = buf . length ;
2006-05-29 05:26:58 +04:00
ops . ooboffs = buf . start & ( mtd - > oobsize - 1 ) ;
ops . datbuf = NULL ;
ops . mode = MTD_OOB_PLACE ;
2006-11-03 18:20:38 +03:00
if ( ops . ooboffs & & ops . ooblen > ( mtd - > oobsize - ops . ooboffs ) )
2006-05-29 05:26:58 +04:00
return - EINVAL ;
ops . oobbuf = kmalloc ( buf . length , GFP_KERNEL ) ;
if ( ! ops . oobbuf )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2005-11-07 14:15:26 +03:00
2006-05-29 05:26:58 +04:00
if ( copy_from_user ( ops . oobbuf , buf . ptr , buf . length ) ) {
kfree ( ops . oobbuf ) ;
2005-04-17 02:20:36 +04:00
return - EFAULT ;
}
2006-05-29 05:26:58 +04:00
buf . start & = ~ ( mtd - > oobsize - 1 ) ;
ret = mtd - > write_oob ( mtd , buf . start , & ops ) ;
2005-04-17 02:20:36 +04:00
2007-12-12 02:44:30 +03:00
if ( ops . oobretlen > 0xFFFFFFFFU )
ret = - EOVERFLOW ;
retlen = ops . oobretlen ;
2008-07-04 10:40:13 +04:00
if ( copy_to_user ( & user_buf - > length , & retlen , sizeof ( buf . length ) ) )
2005-04-17 02:20:36 +04:00
ret = - EFAULT ;
2006-05-29 05:26:58 +04:00
kfree ( ops . oobbuf ) ;
2005-04-17 02:20:36 +04:00
break ;
}
case MEMREADOOB :
{
struct mtd_oob_buf buf ;
2006-05-29 05:26:58 +04:00
struct mtd_oob_ops ops ;
2005-04-17 02:20:36 +04:00
if ( copy_from_user ( & buf , argp , sizeof ( struct mtd_oob_buf ) ) )
return - EFAULT ;
2005-11-07 14:15:26 +03:00
2006-05-29 05:26:58 +04:00
if ( buf . length > 4096 )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
if ( ! mtd - > read_oob )
ret = - EOPNOTSUPP ;
else
ret = access_ok ( VERIFY_WRITE , buf . ptr ,
buf . length ) ? 0 : - EFAULT ;
if ( ret )
return ret ;
2006-06-20 22:05:05 +04:00
ops . ooblen = buf . length ;
2006-05-29 05:26:58 +04:00
ops . ooboffs = buf . start & ( mtd - > oobsize - 1 ) ;
ops . datbuf = NULL ;
ops . mode = MTD_OOB_PLACE ;
2007-04-13 21:50:48 +04:00
if ( ops . ooboffs & & ops . ooblen > ( mtd - > oobsize - ops . ooboffs ) )
2006-05-29 05:26:58 +04:00
return - EINVAL ;
ops . oobbuf = kmalloc ( buf . length , GFP_KERNEL ) ;
if ( ! ops . oobbuf )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2005-11-07 14:15:26 +03:00
2006-05-29 05:26:58 +04:00
buf . start & = ~ ( mtd - > oobsize - 1 ) ;
ret = mtd - > read_oob ( mtd , buf . start , & ops ) ;
2005-04-17 02:20:36 +04:00
2006-11-03 18:20:38 +03:00
if ( put_user ( ops . oobretlen , ( uint32_t __user * ) argp ) )
2005-04-17 02:20:36 +04:00
ret = - EFAULT ;
2006-11-03 18:20:38 +03:00
else if ( ops . oobretlen & & copy_to_user ( buf . ptr , ops . oobbuf ,
ops . oobretlen ) )
2005-04-17 02:20:36 +04:00
ret = - EFAULT ;
2005-11-07 14:15:26 +03:00
2006-05-29 05:26:58 +04:00
kfree ( ops . oobbuf ) ;
2005-04-17 02:20:36 +04:00
break ;
}
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 */
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 ,
. open = mtd_open ,
. release = mtd_close ,
} ;
static int __init init_mtdchar ( void )
{
if ( register_chrdev ( MTD_CHAR_MAJOR , " mtd " , & mtd_fops ) ) {
printk ( KERN_NOTICE " Can't allocate major number %d for Memory Technology Devices. \n " ,
MTD_CHAR_MAJOR ) ;
return - EAGAIN ;
}
2005-06-30 04:23:27 +04:00
mtd_class = class_create ( THIS_MODULE , " mtd " ) ;
if ( IS_ERR ( mtd_class ) ) {
printk ( KERN_ERR " Error creating mtd class. \n " ) ;
unregister_chrdev ( MTD_CHAR_MAJOR , " mtd " ) ;
2005-07-04 21:15:28 +04:00
return PTR_ERR ( mtd_class ) ;
2005-06-30 04:23:27 +04:00
}
register_mtd_user ( & notifier ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void __exit cleanup_mtdchar ( void )
{
2005-06-30 04:23:27 +04:00
unregister_mtd_user ( & notifier ) ;
class_destroy ( mtd_class ) ;
2005-04-17 02:20:36 +04:00
unregister_chrdev ( MTD_CHAR_MAJOR , " mtd " ) ;
}
module_init ( init_mtdchar ) ;
module_exit ( cleanup_mtdchar ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " David Woodhouse <dwmw2@infradead.org> " ) ;
MODULE_DESCRIPTION ( " Direct character-device access to MTD devices " ) ;