2007-07-21 04:37:48 -07:00
/*
* PS3 FLASH ROM Storage Driver
*
* Copyright ( C ) 2007 Sony Computer Entertainment Inc .
* Copyright 2007 Sony Corp .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation ; version 2 of the License .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/uaccess.h>
# include <asm/lv1call.h>
# include <asm/ps3stor.h>
# define DEVICE_NAME "ps3flash"
# define FLASH_BLOCK_SIZE (256*1024)
struct ps3flash_private {
struct mutex mutex ; /* Bounce buffer mutex */
2009-06-10 04:39:07 +00:00
u64 chunk_sectors ;
int tag ; /* Start sector of buffer, -1 if invalid */
bool dirty ;
2007-07-21 04:37:48 -07:00
} ;
static struct ps3_storage_device * ps3flash_dev ;
2009-06-10 04:39:07 +00:00
static int ps3flash_read_write_sectors ( struct ps3_storage_device * dev ,
2009-06-10 04:39:08 +00:00
u64 start_sector , int write )
2007-07-21 04:37:48 -07:00
{
2009-06-10 04:39:08 +00:00
struct ps3flash_private * priv = ps3_system_bus_get_drvdata ( & dev - > sbd ) ;
u64 res = ps3stor_read_write_sectors ( dev , dev - > bounce_lpar ,
start_sector , priv - > chunk_sectors ,
2007-07-21 04:37:48 -07:00
write ) ;
if ( res ) {
2009-01-13 20:06:02 +00:00
dev_err ( & dev - > sbd . core , " %s:%u: %s failed 0x%llx \n " , __func__ ,
2007-07-21 04:37:48 -07:00
__LINE__ , write ? " write " : " read " , res ) ;
return - EIO ;
}
2009-06-10 04:39:07 +00:00
return 0 ;
2007-07-21 04:37:48 -07:00
}
2009-06-10 04:39:07 +00:00
static int ps3flash_writeback ( struct ps3_storage_device * dev )
2007-07-21 04:37:48 -07:00
{
2009-06-10 04:39:07 +00:00
struct ps3flash_private * priv = ps3_system_bus_get_drvdata ( & dev - > sbd ) ;
int res ;
2007-07-21 04:37:48 -07:00
2009-06-10 04:39:07 +00:00
if ( ! priv - > dirty | | priv - > tag < 0 )
return 0 ;
2009-06-10 04:39:08 +00:00
res = ps3flash_read_write_sectors ( dev , priv - > tag , 1 ) ;
2009-06-10 04:39:07 +00:00
if ( res )
return res ;
2007-07-21 04:37:48 -07:00
2009-06-10 04:39:07 +00:00
priv - > dirty = false ;
return 0 ;
2007-07-21 04:37:48 -07:00
}
2009-06-10 04:39:08 +00:00
static int ps3flash_fetch ( struct ps3_storage_device * dev , u64 start_sector )
2007-07-21 04:37:48 -07:00
{
2009-06-10 04:39:07 +00:00
struct ps3flash_private * priv = ps3_system_bus_get_drvdata ( & dev - > sbd ) ;
int res ;
2009-06-10 04:39:08 +00:00
if ( start_sector = = priv - > tag )
2009-06-10 04:39:07 +00:00
return 0 ;
res = ps3flash_writeback ( dev ) ;
if ( res )
return res ;
priv - > tag = - 1 ;
2009-06-10 04:39:08 +00:00
res = ps3flash_read_write_sectors ( dev , start_sector , 0 ) ;
2009-06-10 04:39:07 +00:00
if ( res )
return res ;
2009-06-10 04:39:08 +00:00
priv - > tag = start_sector ;
2009-06-10 04:39:07 +00:00
return 0 ;
2007-07-21 04:37:48 -07:00
}
static loff_t ps3flash_llseek ( struct file * file , loff_t offset , int origin )
{
struct ps3_storage_device * dev = ps3flash_dev ;
loff_t res ;
mutex_lock ( & file - > f_mapping - > host - > i_mutex ) ;
switch ( origin ) {
case 1 :
offset + = file - > f_pos ;
break ;
case 2 :
offset + = dev - > regions [ dev - > region_idx ] . size * dev - > blk_size ;
break ;
}
if ( offset < 0 ) {
res = - EINVAL ;
goto out ;
}
file - > f_pos = offset ;
res = file - > f_pos ;
out :
mutex_unlock ( & file - > f_mapping - > host - > i_mutex ) ;
return res ;
}
2009-06-10 04:39:06 +00:00
static ssize_t ps3flash_read ( char __user * userbuf , void * kernelbuf ,
size_t count , loff_t * pos )
2007-07-21 04:37:48 -07:00
{
struct ps3_storage_device * dev = ps3flash_dev ;
2009-06-10 04:38:55 +00:00
struct ps3flash_private * priv = ps3_system_bus_get_drvdata ( & dev - > sbd ) ;
2009-06-10 04:39:08 +00:00
u64 size , sector , offset ;
2009-06-10 04:39:07 +00:00
int res ;
2007-07-21 04:37:48 -07:00
size_t remaining , n ;
2009-06-10 04:39:06 +00:00
const void * src ;
2007-07-21 04:37:48 -07:00
dev_dbg ( & dev - > sbd . core ,
2009-06-10 04:39:06 +00:00
" %s:%u: Reading %zu bytes at position %lld to U0x%p/K0x%p \n " ,
__func__ , __LINE__ , count , * pos , userbuf , kernelbuf ) ;
2007-07-21 04:37:48 -07:00
size = dev - > regions [ dev - > region_idx ] . size * dev - > blk_size ;
if ( * pos > = size | | ! count )
return 0 ;
if ( * pos + count > size ) {
dev_dbg ( & dev - > sbd . core ,
" %s:%u Truncating count from %zu to %llu \n " , __func__ ,
__LINE__ , count , size - * pos ) ;
count = size - * pos ;
}
2009-06-10 04:39:08 +00:00
sector = * pos / dev - > bounce_size * priv - > chunk_sectors ;
2009-06-10 04:39:07 +00:00
offset = * pos % dev - > bounce_size ;
2007-07-21 04:37:48 -07:00
remaining = count ;
do {
2009-06-10 04:39:08 +00:00
n = min_t ( u64 , remaining , dev - > bounce_size - offset ) ;
src = dev - > bounce_buf + offset ;
2009-06-10 04:39:07 +00:00
2007-07-21 04:37:48 -07:00
mutex_lock ( & priv - > mutex ) ;
2009-06-10 04:39:08 +00:00
res = ps3flash_fetch ( dev , sector ) ;
2009-06-10 04:39:07 +00:00
if ( res )
2007-07-21 04:37:48 -07:00
goto fail ;
dev_dbg ( & dev - > sbd . core ,
2009-06-10 04:39:06 +00:00
" %s:%u: copy %lu bytes from 0x%p to U0x%p/K0x%p \n " ,
__func__ , __LINE__ , n , src , userbuf , kernelbuf ) ;
if ( userbuf ) {
if ( copy_to_user ( userbuf , src , n ) ) {
2009-06-10 04:39:07 +00:00
res = - EFAULT ;
2009-06-10 04:39:06 +00:00
goto fail ;
}
userbuf + = n ;
}
if ( kernelbuf ) {
memcpy ( kernelbuf , src , n ) ;
kernelbuf + = n ;
2007-07-21 04:37:48 -07:00
}
mutex_unlock ( & priv - > mutex ) ;
* pos + = n ;
remaining - = n ;
2009-06-10 04:39:08 +00:00
sector + = priv - > chunk_sectors ;
2007-07-21 04:37:48 -07:00
offset = 0 ;
} while ( remaining > 0 ) ;
return count ;
fail :
2009-06-10 04:39:07 +00:00
mutex_unlock ( & priv - > mutex ) ;
return res ;
2007-07-21 04:37:48 -07:00
}
2009-06-10 04:39:06 +00:00
static ssize_t ps3flash_write ( const char __user * userbuf ,
const void * kernelbuf , size_t count , loff_t * pos )
2007-07-21 04:37:48 -07:00
{
struct ps3_storage_device * dev = ps3flash_dev ;
2009-06-10 04:38:55 +00:00
struct ps3flash_private * priv = ps3_system_bus_get_drvdata ( & dev - > sbd ) ;
2009-06-10 04:39:07 +00:00
u64 size , sector , offset ;
int res = 0 ;
2007-07-21 04:37:48 -07:00
size_t remaining , n ;
2009-06-10 04:39:06 +00:00
void * dst ;
2007-07-21 04:37:48 -07:00
dev_dbg ( & dev - > sbd . core ,
2009-06-10 04:39:06 +00:00
" %s:%u: Writing %zu bytes at position %lld from U0x%p/K0x%p \n " ,
__func__ , __LINE__ , count , * pos , userbuf , kernelbuf ) ;
2007-07-21 04:37:48 -07:00
size = dev - > regions [ dev - > region_idx ] . size * dev - > blk_size ;
if ( * pos > = size | | ! count )
return 0 ;
if ( * pos + count > size ) {
dev_dbg ( & dev - > sbd . core ,
" %s:%u Truncating count from %zu to %llu \n " , __func__ ,
__LINE__ , count , size - * pos ) ;
count = size - * pos ;
}
2009-06-10 04:39:07 +00:00
sector = * pos / dev - > bounce_size * priv - > chunk_sectors ;
2007-07-21 04:37:48 -07:00
offset = * pos % dev - > bounce_size ;
remaining = count ;
do {
2009-06-10 04:39:07 +00:00
n = min_t ( u64 , remaining , dev - > bounce_size - offset ) ;
2009-06-10 04:39:08 +00:00
dst = dev - > bounce_buf + offset ;
2009-06-10 04:39:07 +00:00
2007-07-21 04:37:48 -07:00
mutex_lock ( & priv - > mutex ) ;
2009-06-10 04:39:07 +00:00
if ( n ! = dev - > bounce_size )
2009-06-10 04:39:08 +00:00
res = ps3flash_fetch ( dev , sector ) ;
2009-06-10 04:39:07 +00:00
else if ( sector ! = priv - > tag )
res = ps3flash_writeback ( dev ) ;
if ( res )
goto fail ;
2007-07-21 04:37:48 -07:00
dev_dbg ( & dev - > sbd . core ,
2009-06-10 04:39:06 +00:00
" %s:%u: copy %lu bytes from U0x%p/K0x%p to 0x%p \n " ,
__func__ , __LINE__ , n , userbuf , kernelbuf , dst ) ;
if ( userbuf ) {
if ( copy_from_user ( dst , userbuf , n ) ) {
res = - EFAULT ;
goto fail ;
}
userbuf + = n ;
}
if ( kernelbuf ) {
memcpy ( dst , kernelbuf , n ) ;
kernelbuf + = n ;
2007-07-21 04:37:48 -07:00
}
2009-06-10 04:39:07 +00:00
priv - > tag = sector ;
priv - > dirty = true ;
2007-07-21 04:37:48 -07:00
mutex_unlock ( & priv - > mutex ) ;
* pos + = n ;
remaining - = n ;
2009-06-10 04:39:07 +00:00
sector + = priv - > chunk_sectors ;
2007-07-21 04:37:48 -07:00
offset = 0 ;
} while ( remaining > 0 ) ;
return count ;
fail :
mutex_unlock ( & priv - > mutex ) ;
return res ;
}
2009-06-10 04:39:06 +00:00
static ssize_t ps3flash_user_read ( struct file * file , char __user * buf ,
size_t count , loff_t * pos )
{
return ps3flash_read ( buf , NULL , count , pos ) ;
}
static ssize_t ps3flash_user_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * pos )
{
return ps3flash_write ( buf , NULL , count , pos ) ;
}
static ssize_t ps3flash_kernel_read ( void * buf , size_t count , loff_t pos )
{
return ps3flash_read ( NULL , buf , count , & pos ) ;
}
static ssize_t ps3flash_kernel_write ( const void * buf , size_t count ,
loff_t pos )
{
2009-06-10 04:39:07 +00:00
ssize_t res ;
int wb ;
res = ps3flash_write ( NULL , buf , count , & pos ) ;
if ( res < 0 )
return res ;
/* Make kernel writes synchronous */
wb = ps3flash_writeback ( ps3flash_dev ) ;
if ( wb )
return wb ;
return res ;
2009-06-10 04:39:06 +00:00
}
2009-06-10 04:39:07 +00:00
static int ps3flash_flush ( struct file * file , fl_owner_t id )
{
return ps3flash_writeback ( ps3flash_dev ) ;
}
static int ps3flash_fsync ( struct file * file , struct dentry * dentry ,
int datasync )
{
return ps3flash_writeback ( ps3flash_dev ) ;
}
2007-07-21 04:37:48 -07:00
static irqreturn_t ps3flash_interrupt ( int irq , void * data )
{
struct ps3_storage_device * dev = data ;
int res ;
u64 tag , status ;
res = lv1_storage_get_async_status ( dev - > sbd . dev_id , & tag , & status ) ;
if ( tag ! = dev - > tag )
dev_err ( & dev - > sbd . core ,
2009-01-13 20:06:02 +00:00
" %s:%u: tag mismatch, got %llx, expected %llx \n " ,
2007-07-21 04:37:48 -07:00
__func__ , __LINE__ , tag , dev - > tag ) ;
if ( res ) {
2009-01-13 20:06:02 +00:00
dev_err ( & dev - > sbd . core , " %s:%u: res=%d status=0x%llx \n " ,
2007-07-21 04:37:48 -07:00
__func__ , __LINE__ , res , status ) ;
} else {
dev - > lv1_status = status ;
complete ( & dev - > done ) ;
}
return IRQ_HANDLED ;
}
static const struct file_operations ps3flash_fops = {
. owner = THIS_MODULE ,
. llseek = ps3flash_llseek ,
2009-06-10 04:39:06 +00:00
. read = ps3flash_user_read ,
. write = ps3flash_user_write ,
2009-06-10 04:39:07 +00:00
. flush = ps3flash_flush ,
. fsync = ps3flash_fsync ,
2009-06-10 04:39:06 +00:00
} ;
static const struct ps3_os_area_flash_ops ps3flash_kernel_ops = {
. read = ps3flash_kernel_read ,
. write = ps3flash_kernel_write ,
2007-07-21 04:37:48 -07:00
} ;
static struct miscdevice ps3flash_misc = {
. minor = MISC_DYNAMIC_MINOR ,
. name = DEVICE_NAME ,
. fops = & ps3flash_fops ,
} ;
static int __devinit ps3flash_probe ( struct ps3_system_bus_device * _dev )
{
struct ps3_storage_device * dev = to_ps3_storage_device ( & _dev - > core ) ;
struct ps3flash_private * priv ;
int error ;
unsigned long tmp ;
tmp = dev - > regions [ dev - > region_idx ] . start * dev - > blk_size ;
if ( tmp % FLASH_BLOCK_SIZE ) {
dev_err ( & dev - > sbd . core ,
" %s:%u region start %lu is not aligned \n " , __func__ ,
__LINE__ , tmp ) ;
return - EINVAL ;
}
tmp = dev - > regions [ dev - > region_idx ] . size * dev - > blk_size ;
if ( tmp % FLASH_BLOCK_SIZE ) {
dev_err ( & dev - > sbd . core ,
" %s:%u region size %lu is not aligned \n " , __func__ ,
__LINE__ , tmp ) ;
return - EINVAL ;
}
/* use static buffer, kmalloc cannot allocate 256 KiB */
if ( ! ps3flash_bounce_buffer . address )
return - ENODEV ;
if ( ps3flash_dev ) {
dev_err ( & dev - > sbd . core ,
" Only one FLASH device is supported \n " ) ;
return - EBUSY ;
}
ps3flash_dev = dev ;
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv ) {
error = - ENOMEM ;
goto fail ;
}
2009-06-10 04:38:55 +00:00
ps3_system_bus_set_drvdata ( & dev - > sbd , priv ) ;
2007-07-21 04:37:48 -07:00
mutex_init ( & priv - > mutex ) ;
2009-06-10 04:39:07 +00:00
priv - > tag = - 1 ;
2007-07-21 04:37:48 -07:00
dev - > bounce_size = ps3flash_bounce_buffer . size ;
dev - > bounce_buf = ps3flash_bounce_buffer . address ;
2009-06-10 04:39:07 +00:00
priv - > chunk_sectors = dev - > bounce_size / dev - > blk_size ;
2007-07-21 04:37:48 -07:00
error = ps3stor_setup ( dev , ps3flash_interrupt ) ;
if ( error )
goto fail_free_priv ;
ps3flash_misc . parent = & dev - > sbd . core ;
error = misc_register ( & ps3flash_misc ) ;
if ( error ) {
dev_err ( & dev - > sbd . core , " %s:%u: misc_register failed %d \n " ,
__func__ , __LINE__ , error ) ;
goto fail_teardown ;
}
dev_info ( & dev - > sbd . core , " %s:%u: registered misc device %d \n " ,
__func__ , __LINE__ , ps3flash_misc . minor ) ;
2009-06-10 04:39:06 +00:00
ps3_os_area_flash_register ( & ps3flash_kernel_ops ) ;
2007-07-21 04:37:48 -07:00
return 0 ;
fail_teardown :
ps3stor_teardown ( dev ) ;
fail_free_priv :
kfree ( priv ) ;
2009-06-10 04:38:55 +00:00
ps3_system_bus_set_drvdata ( & dev - > sbd , NULL ) ;
2007-07-21 04:37:48 -07:00
fail :
ps3flash_dev = NULL ;
return error ;
}
static int ps3flash_remove ( struct ps3_system_bus_device * _dev )
{
struct ps3_storage_device * dev = to_ps3_storage_device ( & _dev - > core ) ;
2009-06-10 04:39:06 +00:00
ps3_os_area_flash_register ( NULL ) ;
2007-07-21 04:37:48 -07:00
misc_deregister ( & ps3flash_misc ) ;
ps3stor_teardown ( dev ) ;
2009-06-10 04:38:55 +00:00
kfree ( ps3_system_bus_get_drvdata ( & dev - > sbd ) ) ;
ps3_system_bus_set_drvdata ( & dev - > sbd , NULL ) ;
2007-07-21 04:37:48 -07:00
ps3flash_dev = NULL ;
return 0 ;
}
static struct ps3_system_bus_driver ps3flash = {
. match_id = PS3_MATCH_ID_STOR_FLASH ,
. core . name = DEVICE_NAME ,
. core . owner = THIS_MODULE ,
. probe = ps3flash_probe ,
. remove = ps3flash_remove ,
. shutdown = ps3flash_remove ,
} ;
static int __init ps3flash_init ( void )
{
return ps3_system_bus_driver_register ( & ps3flash ) ;
}
static void __exit ps3flash_exit ( void )
{
ps3_system_bus_driver_unregister ( & ps3flash ) ;
}
module_init ( ps3flash_init ) ;
module_exit ( ps3flash_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " PS3 FLASH ROM Storage Driver " ) ;
MODULE_AUTHOR ( " Sony Corporation " ) ;
MODULE_ALIAS ( PS3_MODULE_ALIAS_STOR_FLASH ) ;