2007-05-07 20:33:32 -04:00
/*
* Char device for device raw access
2006-12-19 19:58:31 -05:00
*
2007-05-07 20:33:32 -04:00
* Copyright ( C ) 2005 - 2007 Kristian Hoegsberg < krh @ bitplanet . net >
2006-12-19 19:58:31 -05:00
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
2009-01-04 16:23:29 +01:00
# include <linux/compat.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/firewire-cdev.h>
# include <linux/idr.h>
2009-01-04 16:23:29 +01:00
# include <linux/jiffies.h>
2006-12-19 19:58:31 -05:00
# include <linux/kernel.h>
2009-01-04 16:23:29 +01:00
# include <linux/kref.h>
2009-01-04 16:23:29 +01:00
# include <linux/mm.h>
# include <linux/module.h>
2008-10-05 10:37:11 +02:00
# include <linux/mutex.h>
2006-12-19 19:58:31 -05:00
# include <linux/poll.h>
2007-09-29 10:41:58 +02:00
# include <linux/preempt.h>
2008-10-03 11:19:09 -04:00
# include <linux/spinlock.h>
2009-01-04 16:23:29 +01:00
# include <linux/time.h>
# include <linux/vmalloc.h>
# include <linux/wait.h>
2009-01-04 16:23:29 +01:00
# include <linux/workqueue.h>
2009-01-04 16:23:29 +01:00
2007-09-29 10:41:58 +02:00
# include <asm/system.h>
2006-12-19 19:58:31 -05:00
# include <asm/uaccess.h>
2009-01-04 16:23:29 +01:00
2006-12-19 19:58:31 -05:00
# include "fw-device.h"
2009-01-04 16:23:29 +01:00
# include "fw-topology.h"
# include "fw-transaction.h"
2006-12-19 19:58:31 -05:00
struct client {
2007-03-07 12:12:43 -05:00
u32 version ;
2006-12-19 19:58:31 -05:00
struct fw_device * device ;
2008-12-21 16:47:17 +01:00
2006-12-19 19:58:31 -05:00
spinlock_t lock ;
2008-12-21 16:47:17 +01:00
bool in_shutdown ;
struct idr resource_idr ;
2006-12-19 19:58:31 -05:00
struct list_head event_list ;
wait_queue_head_t wait ;
2007-03-27 01:43:39 -04:00
u64 bus_reset_closure ;
2007-02-16 17:34:38 -05:00
2006-12-19 19:58:31 -05:00
struct fw_iso_context * iso_context ;
2007-04-30 15:03:14 -04:00
u64 iso_closure ;
2007-02-16 17:34:38 -05:00
struct fw_iso_buffer buffer ;
unsigned long vm_start ;
2007-03-07 12:12:41 -05:00
struct list_head link ;
2009-01-04 16:23:29 +01:00
struct kref kref ;
2006-12-19 19:58:31 -05:00
} ;
2009-01-04 16:23:29 +01:00
static inline void client_get ( struct client * client )
{
kref_get ( & client - > kref ) ;
}
static void client_release ( struct kref * kref )
{
struct client * client = container_of ( kref , struct client , kref ) ;
fw_device_put ( client - > device ) ;
kfree ( client ) ;
}
static void client_put ( struct client * client )
{
kref_put ( & client - > kref , client_release ) ;
}
2009-01-04 16:23:29 +01:00
struct client_resource ;
typedef void ( * client_resource_release_fn_t ) ( struct client * ,
struct client_resource * ) ;
struct client_resource {
client_resource_release_fn_t release ;
int handle ;
} ;
struct address_handler_resource {
struct client_resource resource ;
struct fw_address_handler handler ;
__u64 closure ;
struct client * client ;
} ;
struct outbound_transaction_resource {
struct client_resource resource ;
struct fw_transaction transaction ;
} ;
struct inbound_transaction_resource {
struct client_resource resource ;
struct fw_request * request ;
void * data ;
size_t length ;
} ;
struct descriptor_resource {
struct client_resource resource ;
struct fw_descriptor descriptor ;
u32 data [ 0 ] ;
} ;
2009-01-04 16:23:29 +01:00
struct iso_resource {
struct client_resource resource ;
struct client * client ;
/* Schedule work and access todo only with client->lock held. */
struct delayed_work work ;
2009-01-04 16:23:29 +01:00
enum { ISO_RES_ALLOC , ISO_RES_REALLOC , ISO_RES_DEALLOC ,
ISO_RES_ALLOC_ONCE , ISO_RES_DEALLOC_ONCE , } todo ;
2009-01-04 16:23:29 +01:00
int generation ;
u64 channels ;
s32 bandwidth ;
struct iso_resource_event * e_alloc , * e_dealloc ;
} ;
2009-01-11 13:44:46 +01:00
static void schedule_iso_resource ( struct iso_resource * ) ;
2009-01-04 16:23:29 +01:00
static void release_iso_resource ( struct client * , struct client_resource * ) ;
2009-01-04 16:23:29 +01:00
/*
* dequeue_event ( ) just kfree ( ) ' s the event , so the event has to be
* the first field in a struct XYZ_event .
*/
struct event {
struct { void * data ; size_t size ; } v [ 2 ] ;
struct list_head link ;
} ;
struct bus_reset_event {
struct event event ;
struct fw_cdev_event_bus_reset reset ;
} ;
struct outbound_transaction_event {
struct event event ;
struct client * client ;
struct outbound_transaction_resource r ;
struct fw_cdev_event_response response ;
} ;
struct inbound_transaction_event {
struct event event ;
struct fw_cdev_event_request request ;
} ;
struct iso_interrupt_event {
struct event event ;
struct fw_cdev_event_iso_interrupt interrupt ;
} ;
2009-01-04 16:23:29 +01:00
struct iso_resource_event {
struct event event ;
struct fw_cdev_event_iso_resource resource ;
} ;
2008-12-14 21:47:04 +01:00
static inline void __user * u64_to_uptr ( __u64 value )
2006-12-19 19:58:31 -05:00
{
return ( void __user * ) ( unsigned long ) value ;
}
2008-12-14 21:47:04 +01:00
static inline __u64 uptr_to_u64 ( void __user * ptr )
2006-12-19 19:58:31 -05:00
{
return ( __u64 ) ( unsigned long ) ptr ;
}
static int fw_device_op_open ( struct inode * inode , struct file * file )
{
struct fw_device * device ;
struct client * client ;
2008-02-02 15:01:09 +01:00
device = fw_device_get_by_devt ( inode - > i_rdev ) ;
2007-03-07 12:12:44 -05:00
if ( device = = NULL )
return - ENODEV ;
2006-12-19 19:58:31 -05:00
2008-05-16 11:15:23 -04:00
if ( fw_device_is_shutdown ( device ) ) {
fw_device_put ( device ) ;
return - ENODEV ;
}
2007-05-09 19:23:14 -04:00
client = kzalloc ( sizeof ( * client ) , GFP_KERNEL ) ;
2008-02-02 15:01:09 +01:00
if ( client = = NULL ) {
fw_device_put ( device ) ;
2006-12-19 19:58:31 -05:00
return - ENOMEM ;
2008-02-02 15:01:09 +01:00
}
2006-12-19 19:58:31 -05:00
2008-02-02 15:01:09 +01:00
client - > device = device ;
2006-12-19 19:58:31 -05:00
spin_lock_init ( & client - > lock ) ;
2008-12-21 16:47:17 +01:00
idr_init ( & client - > resource_idr ) ;
INIT_LIST_HEAD ( & client - > event_list ) ;
2006-12-19 19:58:31 -05:00
init_waitqueue_head ( & client - > wait ) ;
2009-01-04 16:23:29 +01:00
kref_init ( & client - > kref ) ;
2006-12-19 19:58:31 -05:00
file - > private_data = client ;
2008-10-05 10:37:11 +02:00
mutex_lock ( & device - > client_list_mutex ) ;
2007-03-07 12:12:41 -05:00
list_add_tail ( & client - > link , & device - > client_list ) ;
2008-10-05 10:37:11 +02:00
mutex_unlock ( & device - > client_list_mutex ) ;
2007-03-07 12:12:41 -05:00
2006-12-19 19:58:31 -05:00
return 0 ;
}
static void queue_event ( struct client * client , struct event * event ,
void * data0 , size_t size0 , void * data1 , size_t size1 )
{
unsigned long flags ;
event - > v [ 0 ] . data = data0 ;
event - > v [ 0 ] . size = size0 ;
event - > v [ 1 ] . data = data1 ;
event - > v [ 1 ] . size = size1 ;
spin_lock_irqsave ( & client - > lock , flags ) ;
2008-12-21 16:47:17 +01:00
if ( client - > in_shutdown )
kfree ( event ) ;
else
list_add_tail ( & event - > link , & client - > event_list ) ;
2006-12-19 19:58:31 -05:00
spin_unlock_irqrestore ( & client - > lock , flags ) ;
2007-10-08 17:00:29 -04:00
wake_up_interruptible ( & client - > wait ) ;
2006-12-19 19:58:31 -05:00
}
2008-12-14 21:47:04 +01:00
static int dequeue_event ( struct client * client ,
char __user * buffer , size_t count )
2006-12-19 19:58:31 -05:00
{
struct event * event ;
size_t size , total ;
2008-12-14 21:45:45 +01:00
int i , ret ;
2006-12-19 19:58:31 -05:00
2008-12-14 21:45:45 +01:00
ret = wait_event_interruptible ( client - > wait ,
! list_empty ( & client - > event_list ) | |
fw_device_is_shutdown ( client - > device ) ) ;
if ( ret < 0 )
return ret ;
2006-12-19 19:58:31 -05:00
2007-03-07 12:12:48 -05:00
if ( list_empty ( & client - > event_list ) & &
fw_device_is_shutdown ( client - > device ) )
return - ENODEV ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
spin_lock_irq ( & client - > lock ) ;
2008-12-21 16:49:57 +01:00
event = list_first_entry ( & client - > event_list , struct event , link ) ;
2006-12-19 19:58:31 -05:00
list_del ( & event - > link ) ;
2009-01-04 16:23:29 +01:00
spin_unlock_irq ( & client - > lock ) ;
2006-12-19 19:58:31 -05:00
total = 0 ;
for ( i = 0 ; i < ARRAY_SIZE ( event - > v ) & & total < count ; i + + ) {
size = min ( event - > v [ i ] . size , count - total ) ;
2007-03-07 12:12:48 -05:00
if ( copy_to_user ( buffer + total , event - > v [ i ] . data , size ) ) {
2008-12-14 21:45:45 +01:00
ret = - EFAULT ;
2006-12-19 19:58:31 -05:00
goto out ;
2007-03-07 12:12:48 -05:00
}
2006-12-19 19:58:31 -05:00
total + = size ;
}
2008-12-14 21:45:45 +01:00
ret = total ;
2006-12-19 19:58:31 -05:00
out :
kfree ( event ) ;
2008-12-14 21:45:45 +01:00
return ret ;
2006-12-19 19:58:31 -05:00
}
2008-12-14 21:47:04 +01:00
static ssize_t fw_device_op_read ( struct file * file , char __user * buffer ,
size_t count , loff_t * offset )
2006-12-19 19:58:31 -05:00
{
struct client * client = file - > private_data ;
return dequeue_event ( client , buffer , count ) ;
}
2008-12-14 21:47:04 +01:00
static void fill_bus_reset_event ( struct fw_cdev_event_bus_reset * event ,
struct client * client )
2007-03-07 12:12:43 -05:00
{
2007-03-27 01:43:39 -04:00
struct fw_card * card = client - > device - > card ;
2008-10-03 11:19:09 -04:00
2009-01-04 16:23:29 +01:00
spin_lock_irq ( & card - > lock ) ;
2007-03-07 12:12:43 -05:00
2007-03-27 01:43:39 -04:00
event - > closure = client - > bus_reset_closure ;
2007-03-07 12:12:43 -05:00
event - > type = FW_CDEV_EVENT_BUS_RESET ;
2008-01-24 01:53:51 +01:00
event - > generation = client - > device - > generation ;
2007-03-27 01:43:39 -04:00
event - > node_id = client - > device - > node_id ;
2007-03-07 12:12:43 -05:00
event - > local_node_id = card - > local_node - > node_id ;
event - > bm_node_id = 0 ; /* FIXME: We don't track the BM. */
event - > irm_node_id = card - > irm_node - > node_id ;
event - > root_node_id = card - > root_node - > node_id ;
2008-10-03 11:19:09 -04:00
2009-01-04 16:23:29 +01:00
spin_unlock_irq ( & card - > lock ) ;
2007-03-07 12:12:43 -05:00
}
2008-12-14 21:47:04 +01:00
static void for_each_client ( struct fw_device * device ,
void ( * callback ) ( struct client * client ) )
2007-03-07 12:12:48 -05:00
{
struct client * c ;
2008-10-05 10:37:11 +02:00
mutex_lock ( & device - > client_list_mutex ) ;
2007-03-07 12:12:48 -05:00
list_for_each_entry ( c , & device - > client_list , link )
callback ( c ) ;
2008-10-05 10:37:11 +02:00
mutex_unlock ( & device - > client_list_mutex ) ;
2007-03-07 12:12:48 -05:00
}
2009-01-04 16:23:29 +01:00
static int schedule_reallocations ( int id , void * p , void * data )
{
struct client_resource * r = p ;
if ( r - > release = = release_iso_resource )
schedule_iso_resource ( container_of ( r ,
struct iso_resource , resource ) ) ;
return 0 ;
}
2008-12-14 21:47:04 +01:00
static void queue_bus_reset_event ( struct client * client )
2007-03-07 12:12:41 -05:00
{
2009-01-04 16:23:29 +01:00
struct bus_reset_event * e ;
2007-03-07 12:12:41 -05:00
2009-01-04 16:23:29 +01:00
e = kzalloc ( sizeof ( * e ) , GFP_KERNEL ) ;
if ( e = = NULL ) {
2007-03-07 12:12:41 -05:00
fw_notify ( " Out of memory when allocating bus reset event \n " ) ;
return ;
}
2009-01-04 16:23:29 +01:00
fill_bus_reset_event ( & e - > reset , client ) ;
2007-03-07 12:12:41 -05:00
2009-01-04 16:23:29 +01:00
queue_event ( client , & e - > event ,
& e - > reset , sizeof ( e - > reset ) , NULL , 0 ) ;
2009-01-04 16:23:29 +01:00
spin_lock_irq ( & client - > lock ) ;
idr_for_each ( & client - > resource_idr , schedule_reallocations , client ) ;
spin_unlock_irq ( & client - > lock ) ;
2007-03-07 12:12:41 -05:00
}
void fw_device_cdev_update ( struct fw_device * device )
{
2007-03-07 12:12:48 -05:00
for_each_client ( device , queue_bus_reset_event ) ;
}
2007-03-07 12:12:41 -05:00
2007-03-07 12:12:48 -05:00
static void wake_up_client ( struct client * client )
{
wake_up_interruptible ( & client - > wait ) ;
}
2007-03-07 12:12:41 -05:00
2007-03-07 12:12:48 -05:00
void fw_device_cdev_remove ( struct fw_device * device )
{
for_each_client ( device , wake_up_client ) ;
2007-03-07 12:12:41 -05:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_get_info ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_get_info * get_info = buffer ;
2007-03-07 12:12:43 -05:00
struct fw_cdev_event_bus_reset bus_reset ;
2008-03-24 20:54:28 +01:00
unsigned long ret = 0 ;
2007-03-07 12:12:43 -05:00
2007-04-30 15:03:13 -04:00
client - > version = get_info - > version ;
get_info - > version = FW_CDEV_VERSION ;
2008-10-03 11:19:09 -04:00
get_info - > card = client - > device - > card - > index ;
2007-03-07 12:12:43 -05:00
2008-03-24 20:54:28 +01:00
down_read ( & fw_device_rwsem ) ;
2007-04-30 15:03:13 -04:00
if ( get_info - > rom ! = 0 ) {
void __user * uptr = u64_to_uptr ( get_info - > rom ) ;
size_t want = get_info - > rom_length ;
2007-03-20 19:42:15 +01:00
size_t have = client - > device - > config_rom_length * 4 ;
2007-03-07 12:12:43 -05:00
2008-03-24 20:54:28 +01:00
ret = copy_to_user ( uptr , client - > device - > config_rom ,
min ( want , have ) ) ;
2007-03-07 12:12:43 -05:00
}
2007-04-30 15:03:13 -04:00
get_info - > rom_length = client - > device - > config_rom_length * 4 ;
2007-03-07 12:12:43 -05:00
2008-03-24 20:54:28 +01:00
up_read ( & fw_device_rwsem ) ;
if ( ret ! = 0 )
return - EFAULT ;
2007-04-30 15:03:13 -04:00
client - > bus_reset_closure = get_info - > bus_reset_closure ;
if ( get_info - > bus_reset ! = 0 ) {
void __user * uptr = u64_to_uptr ( get_info - > bus_reset ) ;
2007-03-07 12:12:43 -05:00
2007-03-27 01:43:39 -04:00
fill_bus_reset_event ( & bus_reset , client ) ;
2007-05-09 19:23:14 -04:00
if ( copy_to_user ( uptr , & bus_reset , sizeof ( bus_reset ) ) )
2007-03-07 12:12:43 -05:00
return - EFAULT ;
}
2006-12-19 19:58:31 -05:00
return 0 ;
}
2008-12-14 21:47:04 +01:00
static int add_client_resource ( struct client * client ,
struct client_resource * resource , gfp_t gfp_mask )
2007-03-27 01:43:41 -04:00
{
unsigned long flags ;
2008-12-21 16:47:17 +01:00
int ret ;
retry :
if ( idr_pre_get ( & client - > resource_idr , gfp_mask ) = = 0 )
return - ENOMEM ;
2007-03-27 01:43:41 -04:00
spin_lock_irqsave ( & client - > lock , flags ) ;
2008-12-21 16:47:17 +01:00
if ( client - > in_shutdown )
ret = - ECANCELED ;
else
ret = idr_get_new ( & client - > resource_idr , resource ,
& resource - > handle ) ;
2009-01-04 16:23:29 +01:00
if ( ret > = 0 ) {
2009-01-04 16:23:29 +01:00
client_get ( client ) ;
2009-01-04 16:23:29 +01:00
if ( resource - > release = = release_iso_resource )
schedule_iso_resource ( container_of ( resource ,
struct iso_resource , resource ) ) ;
}
2007-03-27 01:43:41 -04:00
spin_unlock_irqrestore ( & client - > lock , flags ) ;
2008-12-21 16:47:17 +01:00
if ( ret = = - EAGAIN )
goto retry ;
return ret < 0 ? ret : 0 ;
2007-03-27 01:43:41 -04:00
}
2008-12-14 21:47:04 +01:00
static int release_client_resource ( struct client * client , u32 handle ,
client_resource_release_fn_t release ,
struct client_resource * * resource )
2007-03-27 01:43:41 -04:00
{
struct client_resource * r ;
2009-01-04 16:23:29 +01:00
spin_lock_irq ( & client - > lock ) ;
2008-12-21 16:47:17 +01:00
if ( client - > in_shutdown )
r = NULL ;
else
r = idr_find ( & client - > resource_idr , handle ) ;
if ( r & & r - > release = = release )
idr_remove ( & client - > resource_idr , handle ) ;
2009-01-04 16:23:29 +01:00
spin_unlock_irq ( & client - > lock ) ;
2007-03-27 01:43:41 -04:00
2008-12-21 16:47:17 +01:00
if ( ! ( r & & r - > release = = release ) )
2007-03-27 01:43:41 -04:00
return - EINVAL ;
if ( resource )
* resource = r ;
else
r - > release ( client , r ) ;
2009-01-04 16:23:29 +01:00
client_put ( client ) ;
2007-03-27 01:43:41 -04:00
return 0 ;
}
2008-12-14 21:47:04 +01:00
static void release_transaction ( struct client * client ,
struct client_resource * resource )
2007-03-27 01:43:41 -04:00
{
2009-01-04 16:23:29 +01:00
struct outbound_transaction_resource * r = container_of ( resource ,
struct outbound_transaction_resource , resource ) ;
2007-03-27 01:43:41 -04:00
2009-01-04 16:23:29 +01:00
fw_cancel_transaction ( client - > device - > card , & r - > transaction ) ;
2007-03-27 01:43:41 -04:00
}
2008-12-14 21:47:04 +01:00
static void complete_transaction ( struct fw_card * card , int rcode ,
void * payload , size_t length , void * data )
2006-12-19 19:58:31 -05:00
{
2009-01-04 16:23:29 +01:00
struct outbound_transaction_event * e = data ;
struct fw_cdev_event_response * rsp = & e - > response ;
struct client * client = e - > client ;
2007-03-07 12:12:50 -05:00
unsigned long flags ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
if ( length < rsp - > length )
rsp - > length = length ;
2006-12-19 19:58:31 -05:00
if ( rcode = = RCODE_COMPLETE )
2009-01-04 16:23:29 +01:00
memcpy ( rsp - > data , payload , rsp - > length ) ;
2006-12-19 19:58:31 -05:00
2007-03-07 12:12:50 -05:00
spin_lock_irqsave ( & client - > lock , flags ) ;
2008-12-21 16:47:17 +01:00
/*
2009-01-04 16:23:29 +01:00
* 1. If called while in shutdown , the idr tree must be left untouched .
* The idr handle will be removed and the client reference will be
* dropped later .
* 2. If the call chain was release_client_resource - >
* release_transaction - > complete_transaction ( instead of a normal
* conclusion of the transaction ) , i . e . if this resource was already
* unregistered from the idr , the client reference will be dropped
* by release_client_resource and we must not drop it here .
2008-12-21 16:47:17 +01:00
*/
2009-01-04 16:23:29 +01:00
if ( ! client - > in_shutdown & &
2009-01-04 16:23:29 +01:00
idr_find ( & client - > resource_idr , e - > r . resource . handle ) ) {
idr_remove ( & client - > resource_idr , e - > r . resource . handle ) ;
2009-01-04 16:23:29 +01:00
/* Drop the idr's reference */
client_put ( client ) ;
}
2007-03-07 12:12:50 -05:00
spin_unlock_irqrestore ( & client - > lock , flags ) ;
2009-01-04 16:23:29 +01:00
rsp - > type = FW_CDEV_EVENT_RESPONSE ;
rsp - > rcode = rcode ;
2008-07-29 23:46:25 -07:00
/*
2009-01-04 16:23:29 +01:00
* In the case that sizeof ( * rsp ) doesn ' t align with the position of the
2008-07-29 23:46:25 -07:00
* data , and the read is short , preserve an extra copy of the data
* to stay compatible with a pre - 2.6 .27 bug . Since the bug is harmless
* for short reads and some apps depended on it , this is both safe
* and prudent for compatibility .
*/
2009-01-04 16:23:29 +01:00
if ( rsp - > length < = sizeof ( * rsp ) - offsetof ( typeof ( * rsp ) , data ) )
queue_event ( client , & e - > event , rsp , sizeof ( * rsp ) ,
rsp - > data , rsp - > length ) ;
2008-07-29 23:46:25 -07:00
else
2009-01-04 16:23:29 +01:00
queue_event ( client , & e - > event , rsp , sizeof ( * rsp ) + rsp - > length ,
2008-07-29 23:46:25 -07:00
NULL , 0 ) ;
2009-01-04 16:23:29 +01:00
/* Drop the transaction callback's reference */
client_put ( client ) ;
2006-12-19 19:58:31 -05:00
}
2009-01-04 16:23:29 +01:00
static int init_request ( struct client * client ,
struct fw_cdev_send_request * request ,
int destination_id , int speed )
2006-12-19 19:58:31 -05:00
{
2009-01-04 16:23:29 +01:00
struct outbound_transaction_event * e ;
2008-12-05 22:44:42 +01:00
int ret ;
2006-12-19 19:58:31 -05:00
2009-03-10 21:02:21 +01:00
if ( request - > tcode ! = TCODE_STREAM_DATA & &
( request - > length > 4096 | | request - > length > 512 < < speed ) )
2009-01-04 16:23:29 +01:00
return - EIO ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
e = kmalloc ( sizeof ( * e ) + request - > length , GFP_KERNEL ) ;
if ( e = = NULL )
2006-12-19 19:58:31 -05:00
return - ENOMEM ;
2009-01-04 16:23:29 +01:00
e - > client = client ;
e - > response . length = request - > length ;
e - > response . closure = request - > closure ;
2006-12-19 19:58:31 -05:00
2007-04-30 15:03:13 -04:00
if ( request - > data & &
2009-01-04 16:23:29 +01:00
copy_from_user ( e - > response . data ,
2007-04-30 15:03:13 -04:00
u64_to_uptr ( request - > data ) , request - > length ) ) {
2008-12-05 22:44:42 +01:00
ret = - EFAULT ;
2008-12-21 16:47:17 +01:00
goto failed ;
2008-12-05 22:44:42 +01:00
}
2009-01-04 16:23:29 +01:00
e - > r . resource . release = release_transaction ;
ret = add_client_resource ( client , & e - > r . resource , GFP_KERNEL ) ;
2008-12-21 16:47:17 +01:00
if ( ret < 0 )
goto failed ;
2007-03-07 12:12:50 -05:00
2009-01-04 16:23:29 +01:00
/* Get a reference for the transaction callback */
client_get ( client ) ;
2009-01-04 16:23:29 +01:00
fw_send_request ( client - > device - > card , & e - > r . transaction ,
2009-03-10 21:01:54 +01:00
request - > tcode , destination_id , request - > generation ,
speed , request - > offset , e - > response . data ,
request - > length , complete_transaction , e ) ;
return 0 ;
2006-12-19 19:58:31 -05:00
2008-12-21 16:47:17 +01:00
failed :
2009-01-04 16:23:29 +01:00
kfree ( e ) ;
2008-12-05 22:44:42 +01:00
return ret ;
2006-12-19 19:58:31 -05:00
}
2009-01-04 16:23:29 +01:00
static int ioctl_send_request ( struct client * client , void * buffer )
{
struct fw_cdev_send_request * request = buffer ;
switch ( request - > tcode ) {
case TCODE_WRITE_QUADLET_REQUEST :
case TCODE_WRITE_BLOCK_REQUEST :
case TCODE_READ_QUADLET_REQUEST :
case TCODE_READ_BLOCK_REQUEST :
case TCODE_LOCK_MASK_SWAP :
case TCODE_LOCK_COMPARE_SWAP :
case TCODE_LOCK_FETCH_ADD :
case TCODE_LOCK_LITTLE_ADD :
case TCODE_LOCK_BOUNDED_ADD :
case TCODE_LOCK_WRAP_ADD :
case TCODE_LOCK_VENDOR_DEPENDENT :
break ;
default :
return - EINVAL ;
}
2009-03-10 21:01:08 +01:00
return init_request ( client , request , client - > device - > node_id ,
2009-01-04 16:23:29 +01:00
client - > device - > max_speed ) ;
}
2008-12-14 21:47:04 +01:00
static void release_request ( struct client * client ,
struct client_resource * resource )
2007-03-27 01:43:41 -04:00
{
2009-01-04 16:23:29 +01:00
struct inbound_transaction_resource * r = container_of ( resource ,
struct inbound_transaction_resource , resource ) ;
2007-03-27 01:43:41 -04:00
2009-01-04 16:23:29 +01:00
fw_send_response ( client - > device - > card , r - > request ,
2007-03-27 01:43:41 -04:00
RCODE_CONFLICT_ERROR ) ;
2009-01-04 16:23:29 +01:00
kfree ( r ) ;
2007-03-27 01:43:41 -04:00
}
2009-01-04 16:23:29 +01:00
static void handle_request ( struct fw_card * card , struct fw_request * request ,
2008-12-14 21:47:04 +01:00
int tcode , int destination , int source ,
int generation , int speed ,
unsigned long long offset ,
void * payload , size_t length , void * callback_data )
2006-12-19 19:58:31 -05:00
{
2009-01-04 16:23:29 +01:00
struct address_handler_resource * handler = callback_data ;
struct inbound_transaction_resource * r ;
struct inbound_transaction_event * e ;
2008-12-21 16:47:17 +01:00
int ret ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
r = kmalloc ( sizeof ( * r ) , GFP_ATOMIC ) ;
2007-05-09 19:23:14 -04:00
e = kmalloc ( sizeof ( * e ) , GFP_ATOMIC ) ;
2009-01-04 16:23:29 +01:00
if ( r = = NULL | | e = = NULL )
2008-12-21 16:47:17 +01:00
goto failed ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
r - > request = request ;
r - > data = payload ;
r - > length = length ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
r - > resource . release = release_request ;
ret = add_client_resource ( handler - > client , & r - > resource , GFP_ATOMIC ) ;
2008-12-21 16:47:17 +01:00
if ( ret < 0 )
goto failed ;
2006-12-19 19:58:31 -05:00
e - > request . type = FW_CDEV_EVENT_REQUEST ;
e - > request . tcode = tcode ;
e - > request . offset = offset ;
e - > request . length = length ;
2009-01-04 16:23:29 +01:00
e - > request . handle = r - > resource . handle ;
2006-12-19 19:58:31 -05:00
e - > request . closure = handler - > closure ;
2009-01-04 16:23:29 +01:00
queue_event ( handler - > client , & e - > event ,
2007-05-09 19:23:14 -04:00
& e - > request , sizeof ( e - > request ) , payload , length ) ;
2008-12-21 16:47:17 +01:00
return ;
failed :
2009-01-04 16:23:29 +01:00
kfree ( r ) ;
2008-12-21 16:47:17 +01:00
kfree ( e ) ;
2009-01-04 16:23:29 +01:00
fw_send_response ( card , request , RCODE_CONFLICT_ERROR ) ;
2006-12-19 19:58:31 -05:00
}
2008-12-14 21:47:04 +01:00
static void release_address_handler ( struct client * client ,
struct client_resource * resource )
2007-03-27 01:43:41 -04:00
{
2009-01-04 16:23:29 +01:00
struct address_handler_resource * r =
container_of ( resource , struct address_handler_resource , resource ) ;
2007-03-27 01:43:41 -04:00
2009-01-04 16:23:29 +01:00
fw_core_remove_address_handler ( & r - > handler ) ;
kfree ( r ) ;
2007-03-27 01:43:41 -04:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_allocate ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_allocate * request = buffer ;
2009-01-04 16:23:29 +01:00
struct address_handler_resource * r ;
2006-12-19 19:58:31 -05:00
struct fw_address_region region ;
2008-12-21 16:47:17 +01:00
int ret ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
r = kmalloc ( sizeof ( * r ) , GFP_KERNEL ) ;
if ( r = = NULL )
2006-12-19 19:58:31 -05:00
return - ENOMEM ;
2007-04-30 15:03:13 -04:00
region . start = request - > offset ;
region . end = request - > offset + request - > length ;
2009-01-04 16:23:29 +01:00
r - > handler . length = request - > length ;
r - > handler . address_callback = handle_request ;
r - > handler . callback_data = r ;
r - > closure = request - > closure ;
r - > client = client ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
ret = fw_core_add_address_handler ( & r - > handler , & region ) ;
2008-12-14 19:21:01 +01:00
if ( ret < 0 ) {
2009-01-04 16:23:29 +01:00
kfree ( r ) ;
2008-12-14 19:21:01 +01:00
return ret ;
2006-12-19 19:58:31 -05:00
}
2009-01-04 16:23:29 +01:00
r - > resource . release = release_address_handler ;
ret = add_client_resource ( client , & r - > resource , GFP_KERNEL ) ;
2008-12-21 16:47:17 +01:00
if ( ret < 0 ) {
2009-01-04 16:23:29 +01:00
release_address_handler ( client , & r - > resource ) ;
2008-12-21 16:47:17 +01:00
return ret ;
}
2009-01-04 16:23:29 +01:00
request - > handle = r - > resource . handle ;
2006-12-19 19:58:31 -05:00
return 0 ;
}
2007-04-30 15:03:13 -04:00
static int ioctl_deallocate ( struct client * client , void * buffer )
2007-03-14 17:34:55 -04:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_deallocate * request = buffer ;
2007-03-14 17:34:55 -04:00
2008-12-21 16:47:17 +01:00
return release_client_resource ( client , request - > handle ,
release_address_handler , NULL ) ;
2007-03-14 17:34:55 -04:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_send_response ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_send_response * request = buffer ;
2007-03-27 01:43:41 -04:00
struct client_resource * resource ;
2009-01-04 16:23:29 +01:00
struct inbound_transaction_resource * r ;
2006-12-19 19:58:31 -05:00
2008-12-21 16:47:17 +01:00
if ( release_client_resource ( client , request - > handle ,
release_request , & resource ) < 0 )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
2008-12-21 16:47:17 +01:00
2009-01-04 16:23:29 +01:00
r = container_of ( resource , struct inbound_transaction_resource ,
resource ) ;
2007-04-30 15:03:13 -04:00
if ( request - > length < r - > length )
r - > length = request - > length ;
if ( copy_from_user ( r - > data , u64_to_uptr ( request - > data ) , r - > length ) )
2006-12-19 19:58:31 -05:00
return - EFAULT ;
2007-04-30 15:03:13 -04:00
fw_send_response ( client - > device - > card , r - > request , request - > rcode ) ;
2006-12-19 19:58:31 -05:00
kfree ( r ) ;
return 0 ;
}
2007-04-30 15:03:13 -04:00
static int ioctl_initiate_bus_reset ( struct client * client , void * buffer )
2007-03-07 12:12:42 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_initiate_bus_reset * request = buffer ;
2007-03-07 12:12:42 -05:00
int short_reset ;
2007-04-30 15:03:13 -04:00
short_reset = ( request - > type = = FW_CDEV_SHORT_RESET ) ;
2007-03-07 12:12:42 -05:00
return fw_core_initiate_bus_reset ( client - > device - > card , short_reset ) ;
}
2007-03-27 01:43:41 -04:00
static void release_descriptor ( struct client * client ,
struct client_resource * resource )
{
2009-01-04 16:23:29 +01:00
struct descriptor_resource * r =
container_of ( resource , struct descriptor_resource , resource ) ;
2007-03-27 01:43:41 -04:00
2009-01-04 16:23:29 +01:00
fw_core_remove_descriptor ( & r - > descriptor ) ;
kfree ( r ) ;
2007-03-27 01:43:41 -04:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_add_descriptor ( struct client * client , void * buffer )
2007-03-28 21:26:42 +02:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_add_descriptor * request = buffer ;
2009-03-10 21:00:23 +01:00
struct fw_card * card = client - > device - > card ;
2009-01-04 16:23:29 +01:00
struct descriptor_resource * r ;
2008-12-21 16:47:17 +01:00
int ret ;
2007-03-28 21:26:42 +02:00
2009-03-10 21:00:23 +01:00
/* Access policy: Allow this ioctl only on local nodes' device files. */
spin_lock_irq ( & card - > lock ) ;
ret = client - > device - > node_id ! = card - > local_node - > node_id ;
spin_unlock_irq ( & card - > lock ) ;
if ( ret )
return - ENOSYS ;
2007-04-30 15:03:13 -04:00
if ( request - > length > 256 )
2007-03-28 21:26:42 +02:00
return - EINVAL ;
2009-01-04 16:23:29 +01:00
r = kmalloc ( sizeof ( * r ) + request - > length * 4 , GFP_KERNEL ) ;
if ( r = = NULL )
2007-03-28 21:26:42 +02:00
return - ENOMEM ;
2009-01-04 16:23:29 +01:00
if ( copy_from_user ( r - > data ,
2007-04-30 15:03:13 -04:00
u64_to_uptr ( request - > data ) , request - > length * 4 ) ) {
2008-12-21 16:47:17 +01:00
ret = - EFAULT ;
goto failed ;
2007-03-28 21:26:42 +02:00
}
2009-01-04 16:23:29 +01:00
r - > descriptor . length = request - > length ;
r - > descriptor . immediate = request - > immediate ;
r - > descriptor . key = request - > key ;
r - > descriptor . data = r - > data ;
2007-03-28 21:26:42 +02:00
2009-01-04 16:23:29 +01:00
ret = fw_core_add_descriptor ( & r - > descriptor ) ;
2008-12-21 16:47:17 +01:00
if ( ret < 0 )
goto failed ;
2007-03-28 21:26:42 +02:00
2009-01-04 16:23:29 +01:00
r - > resource . release = release_descriptor ;
ret = add_client_resource ( client , & r - > resource , GFP_KERNEL ) ;
2008-12-21 16:47:17 +01:00
if ( ret < 0 ) {
2009-01-04 16:23:29 +01:00
fw_core_remove_descriptor ( & r - > descriptor ) ;
2008-12-21 16:47:17 +01:00
goto failed ;
}
2009-01-04 16:23:29 +01:00
request - > handle = r - > resource . handle ;
2007-03-28 21:26:42 +02:00
return 0 ;
2008-12-21 16:47:17 +01:00
failed :
2009-01-04 16:23:29 +01:00
kfree ( r ) ;
2008-12-21 16:47:17 +01:00
return ret ;
2007-03-28 21:26:42 +02:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_remove_descriptor ( struct client * client , void * buffer )
2007-03-28 21:26:42 +02:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_remove_descriptor * request = buffer ;
2007-03-28 21:26:42 +02:00
2008-12-21 16:47:17 +01:00
return release_client_resource ( client , request - > handle ,
release_descriptor , NULL ) ;
2007-03-28 21:26:42 +02:00
}
2008-12-14 21:47:04 +01:00
static void iso_callback ( struct fw_iso_context * context , u32 cycle ,
size_t header_length , void * header , void * data )
2006-12-19 19:58:31 -05:00
{
struct client * client = data ;
2009-01-04 16:23:29 +01:00
struct iso_interrupt_event * e ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
e = kzalloc ( sizeof ( * e ) + header_length , GFP_ATOMIC ) ;
if ( e = = NULL )
2006-12-19 19:58:31 -05:00
return ;
2009-01-04 16:23:29 +01:00
e - > interrupt . type = FW_CDEV_EVENT_ISO_INTERRUPT ;
e - > interrupt . closure = client - > iso_closure ;
e - > interrupt . cycle = cycle ;
e - > interrupt . header_length = header_length ;
memcpy ( e - > interrupt . header , header , header_length ) ;
queue_event ( client , & e - > event , & e - > interrupt ,
sizeof ( e - > interrupt ) + header_length , NULL , 0 ) ;
2006-12-19 19:58:31 -05:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_create_iso_context ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_create_iso_context * request = buffer ;
2007-06-20 17:48:07 -04:00
struct fw_iso_context * context ;
2006-12-19 19:58:31 -05:00
2008-02-20 21:10:06 +01:00
/* We only support one context at this time. */
if ( client - > iso_context ! = NULL )
return - EBUSY ;
2007-04-30 15:03:13 -04:00
if ( request - > channel > 63 )
2007-02-16 17:34:50 -05:00
return - EINVAL ;
2007-04-30 15:03:13 -04:00
switch ( request - > type ) {
2007-03-14 17:34:53 -04:00
case FW_ISO_CONTEXT_RECEIVE :
2007-04-30 15:03:13 -04:00
if ( request - > header_size < 4 | | ( request - > header_size & 3 ) )
2007-03-14 17:34:53 -04:00
return - EINVAL ;
2007-02-16 17:34:51 -05:00
2007-03-14 17:34:53 -04:00
break ;
case FW_ISO_CONTEXT_TRANSMIT :
2007-04-30 15:03:13 -04:00
if ( request - > speed > SCODE_3200 )
2007-03-14 17:34:53 -04:00
return - EINVAL ;
break ;
default :
2007-02-16 17:34:50 -05:00
return - EINVAL ;
2007-03-14 17:34:53 -04:00
}
2007-06-20 17:48:07 -04:00
context = fw_iso_context_create ( client - > device - > card ,
request - > type ,
request - > channel ,
request - > speed ,
request - > header_size ,
iso_callback , client ) ;
if ( IS_ERR ( context ) )
return PTR_ERR ( context ) ;
2007-04-30 15:03:14 -04:00
client - > iso_closure = request - > closure ;
2007-06-20 17:48:07 -04:00
client - > iso_context = context ;
2006-12-19 19:58:31 -05:00
2007-04-30 15:03:14 -04:00
/* We only support one context at this time. */
request - > handle = 0 ;
2006-12-19 19:58:31 -05:00
return 0 ;
}
2007-05-31 11:16:43 -04:00
/* Macros for decoding the iso packet control header. */
# define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff)
# define GET_INTERRUPT(v) (((v) >> 16) & 0x01)
# define GET_SKIP(v) (((v) >> 17) & 0x01)
2008-09-12 18:09:55 +02:00
# define GET_TAG(v) (((v) >> 18) & 0x03)
# define GET_SY(v) (((v) >> 20) & 0x0f)
2007-05-31 11:16:43 -04:00
# define GET_HEADER_LENGTH(v) (((v) >> 24) & 0xff)
2007-04-30 15:03:13 -04:00
static int ioctl_queue_iso ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_queue_iso * request = buffer ;
2006-12-19 19:58:31 -05:00
struct fw_cdev_iso_packet __user * p , * end , * next ;
2007-02-16 17:34:44 -05:00
struct fw_iso_context * ctx = client - > iso_context ;
2007-03-28 20:46:23 +02:00
unsigned long payload , buffer_end , header_length ;
2007-05-31 11:16:43 -04:00
u32 control ;
2006-12-19 19:58:31 -05:00
int count ;
struct {
struct fw_iso_packet packet ;
u8 header [ 256 ] ;
} u ;
2007-04-30 15:03:14 -04:00
if ( ctx = = NULL | | request - > handle ! = 0 )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
2007-05-07 20:33:32 -04:00
/*
* If the user passes a non - NULL data pointer , has mmap ( ) ' ed
2006-12-19 19:58:31 -05:00
* the iso buffer , and the pointer points inside the buffer ,
* we setup the payload pointers accordingly . Otherwise we
2007-02-16 17:34:38 -05:00
* set them both to 0 , which will still let packets with
2006-12-19 19:58:31 -05:00
* payload_length = = 0 through . In other words , if no packets
* use the indirect payload , the iso buffer need not be mapped
2007-05-07 20:33:32 -04:00
* and the request - > data pointer is ignored .
*/
2006-12-19 19:58:31 -05:00
2007-04-30 15:03:13 -04:00
payload = ( unsigned long ) request - > data - client - > vm_start ;
2007-03-28 20:46:23 +02:00
buffer_end = client - > buffer . page_count < < PAGE_SHIFT ;
2007-04-30 15:03:13 -04:00
if ( request - > data = = 0 | | client - > buffer . pages = = NULL | |
2007-03-28 20:46:23 +02:00
payload > = buffer_end ) {
2007-02-16 17:34:38 -05:00
payload = 0 ;
2007-03-28 20:46:23 +02:00
buffer_end = 0 ;
2006-12-19 19:58:31 -05:00
}
2007-10-14 19:34:40 +01:00
p = ( struct fw_cdev_iso_packet __user * ) u64_to_uptr ( request - > packets ) ;
if ( ! access_ok ( VERIFY_READ , p , request - > size ) )
2006-12-19 19:58:31 -05:00
return - EFAULT ;
2007-04-30 15:03:13 -04:00
end = ( void __user * ) p + request - > size ;
2006-12-19 19:58:31 -05:00
count = 0 ;
while ( p < end ) {
2007-05-31 11:16:43 -04:00
if ( get_user ( control , & p - > control ) )
2006-12-19 19:58:31 -05:00
return - EFAULT ;
2007-05-31 11:16:43 -04:00
u . packet . payload_length = GET_PAYLOAD_LENGTH ( control ) ;
u . packet . interrupt = GET_INTERRUPT ( control ) ;
u . packet . skip = GET_SKIP ( control ) ;
u . packet . tag = GET_TAG ( control ) ;
u . packet . sy = GET_SY ( control ) ;
u . packet . header_length = GET_HEADER_LENGTH ( control ) ;
2007-02-16 17:34:40 -05:00
2007-02-16 17:34:44 -05:00
if ( ctx - > type = = FW_ISO_CONTEXT_TRANSMIT ) {
2007-02-16 17:34:40 -05:00
header_length = u . packet . header_length ;
} else {
2007-05-07 20:33:32 -04:00
/*
* We require that header_length is a multiple of
* the fixed header size , ctx - > header_size .
*/
2007-02-16 17:34:44 -05:00
if ( ctx - > header_size = = 0 ) {
if ( u . packet . header_length > 0 )
return - EINVAL ;
} else if ( u . packet . header_length % ctx - > header_size ! = 0 ) {
2007-02-16 17:34:40 -05:00
return - EINVAL ;
2007-02-16 17:34:44 -05:00
}
2007-02-16 17:34:40 -05:00
header_length = 0 ;
}
2006-12-19 19:58:31 -05:00
next = ( struct fw_cdev_iso_packet __user * )
2007-02-16 17:34:40 -05:00
& p - > header [ header_length / 4 ] ;
2006-12-19 19:58:31 -05:00
if ( next > end )
return - EINVAL ;
if ( __copy_from_user
2007-02-16 17:34:40 -05:00
( u . packet . header , p - > header , header_length ) )
2006-12-19 19:58:31 -05:00
return - EFAULT ;
2007-02-16 17:34:51 -05:00
if ( u . packet . skip & & ctx - > type = = FW_ISO_CONTEXT_TRANSMIT & &
2006-12-19 19:58:31 -05:00
u . packet . header_length + u . packet . payload_length > 0 )
return - EINVAL ;
2007-03-28 20:46:23 +02:00
if ( payload + u . packet . payload_length > buffer_end )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
2007-02-16 17:34:44 -05:00
if ( fw_iso_context_queue ( ctx , & u . packet ,
& client - > buffer , payload ) )
2006-12-19 19:58:31 -05:00
break ;
p = next ;
payload + = u . packet . payload_length ;
count + + ;
}
2007-04-30 15:03:13 -04:00
request - > size - = uptr_to_u64 ( p ) - request - > packets ;
request - > packets = uptr_to_u64 ( p ) ;
request - > data = client - > vm_start + payload ;
2006-12-19 19:58:31 -05:00
return count ;
}
2007-04-30 15:03:13 -04:00
static int ioctl_start_iso ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
struct fw_cdev_start_iso * request = buffer ;
2006-12-19 19:58:31 -05:00
2008-02-20 21:10:06 +01:00
if ( client - > iso_context = = NULL | | request - > handle ! = 0 )
2007-04-30 15:03:14 -04:00
return - EINVAL ;
2008-02-20 21:10:06 +01:00
2007-03-14 17:34:54 -04:00
if ( client - > iso_context - > type = = FW_ISO_CONTEXT_RECEIVE ) {
2007-04-30 15:03:13 -04:00
if ( request - > tags = = 0 | | request - > tags > 15 )
2007-03-14 17:34:54 -04:00
return - EINVAL ;
2007-04-30 15:03:13 -04:00
if ( request - > sync > 15 )
2007-03-14 17:34:54 -04:00
return - EINVAL ;
}
2007-04-30 15:03:13 -04:00
return fw_iso_context_start ( client - > iso_context , request - > cycle ,
request - > sync , request - > tags ) ;
2006-12-19 19:58:31 -05:00
}
2007-04-30 15:03:13 -04:00
static int ioctl_stop_iso ( struct client * client , void * buffer )
2007-02-16 17:34:42 -05:00
{
2007-04-30 15:03:14 -04:00
struct fw_cdev_stop_iso * request = buffer ;
2008-02-20 21:10:06 +01:00
if ( client - > iso_context = = NULL | | request - > handle ! = 0 )
2007-04-30 15:03:14 -04:00
return - EINVAL ;
2007-02-16 17:34:42 -05:00
return fw_iso_context_stop ( client - > iso_context ) ;
}
2007-09-29 10:41:58 +02:00
static int ioctl_get_cycle_timer ( struct client * client , void * buffer )
{
struct fw_cdev_get_cycle_timer * request = buffer ;
struct fw_card * card = client - > device - > card ;
unsigned long long bus_time ;
struct timeval tv ;
unsigned long flags ;
preempt_disable ( ) ;
local_irq_save ( flags ) ;
bus_time = card - > driver - > get_bus_time ( card ) ;
do_gettimeofday ( & tv ) ;
local_irq_restore ( flags ) ;
preempt_enable ( ) ;
request - > local_time = tv . tv_sec * 1000000ULL + tv . tv_usec ;
request - > cycle_timer = bus_time & 0xffffffff ;
return 0 ;
}
2009-01-04 16:23:29 +01:00
static void iso_resource_work ( struct work_struct * work )
{
struct iso_resource_event * e ;
struct iso_resource * r =
container_of ( work , struct iso_resource , work . work ) ;
struct client * client = r - > client ;
int generation , channel , bandwidth , todo ;
bool skip , free , success ;
spin_lock_irq ( & client - > lock ) ;
generation = client - > device - > generation ;
todo = r - > todo ;
/* Allow 1000ms grace period for other reallocations. */
if ( todo = = ISO_RES_ALLOC & &
time_is_after_jiffies ( client - > device - > card - > reset_jiffies + HZ ) ) {
if ( schedule_delayed_work ( & r - > work , DIV_ROUND_UP ( HZ , 3 ) ) )
client_get ( client ) ;
skip = true ;
} else {
/* We could be called twice within the same generation. */
skip = todo = = ISO_RES_REALLOC & &
r - > generation = = generation ;
}
2009-01-04 16:23:29 +01:00
free = todo = = ISO_RES_DEALLOC | |
todo = = ISO_RES_ALLOC_ONCE | |
todo = = ISO_RES_DEALLOC_ONCE ;
2009-01-04 16:23:29 +01:00
r - > generation = generation ;
spin_unlock_irq ( & client - > lock ) ;
if ( skip )
goto out ;
bandwidth = r - > bandwidth ;
fw_iso_resource_manage ( client - > device - > card , generation ,
r - > channels , & channel , & bandwidth ,
2009-01-04 16:23:29 +01:00
todo = = ISO_RES_ALLOC | |
todo = = ISO_RES_REALLOC | |
todo = = ISO_RES_ALLOC_ONCE ) ;
2009-01-04 16:23:29 +01:00
/*
* Is this generation outdated already ? As long as this resource sticks
* in the idr , it will be scheduled again for a newer generation or at
* shutdown .
*/
if ( channel = = - EAGAIN & &
( todo = = ISO_RES_ALLOC | | todo = = ISO_RES_REALLOC ) )
goto out ;
success = channel > = 0 | | bandwidth > 0 ;
spin_lock_irq ( & client - > lock ) ;
/*
* Transit from allocation to reallocation , except if the client
* requested deallocation in the meantime .
*/
if ( r - > todo = = ISO_RES_ALLOC )
r - > todo = ISO_RES_REALLOC ;
/*
* Allocation or reallocation failure ? Pull this resource out of the
* idr and prepare for deletion , unless the client is shutting down .
*/
if ( r - > todo = = ISO_RES_REALLOC & & ! success & &
! client - > in_shutdown & &
idr_find ( & client - > resource_idr , r - > resource . handle ) ) {
idr_remove ( & client - > resource_idr , r - > resource . handle ) ;
client_put ( client ) ;
free = true ;
}
spin_unlock_irq ( & client - > lock ) ;
if ( todo = = ISO_RES_ALLOC & & channel > = 0 )
2009-01-08 23:07:40 +01:00
r - > channels = 1ULL < < channel ;
2009-01-04 16:23:29 +01:00
if ( todo = = ISO_RES_REALLOC & & success )
goto out ;
2009-01-04 16:23:29 +01:00
if ( todo = = ISO_RES_ALLOC | | todo = = ISO_RES_ALLOC_ONCE ) {
2009-01-04 16:23:29 +01:00
e = r - > e_alloc ;
r - > e_alloc = NULL ;
} else {
e = r - > e_dealloc ;
r - > e_dealloc = NULL ;
}
e - > resource . handle = r - > resource . handle ;
e - > resource . channel = channel ;
e - > resource . bandwidth = bandwidth ;
queue_event ( client , & e - > event ,
& e - > resource , sizeof ( e - > resource ) , NULL , 0 ) ;
if ( free ) {
cancel_delayed_work ( & r - > work ) ;
kfree ( r - > e_alloc ) ;
kfree ( r - > e_dealloc ) ;
kfree ( r ) ;
}
out :
client_put ( client ) ;
}
2009-01-11 13:44:46 +01:00
static void schedule_iso_resource ( struct iso_resource * r )
2009-01-04 16:23:29 +01:00
{
2009-01-04 16:23:29 +01:00
client_get ( r - > client ) ;
2009-01-11 13:44:46 +01:00
if ( ! schedule_delayed_work ( & r - > work , 0 ) )
2009-01-04 16:23:29 +01:00
client_put ( r - > client ) ;
2009-01-04 16:23:29 +01:00
}
static void release_iso_resource ( struct client * client ,
struct client_resource * resource )
{
struct iso_resource * r =
container_of ( resource , struct iso_resource , resource ) ;
spin_lock_irq ( & client - > lock ) ;
r - > todo = ISO_RES_DEALLOC ;
schedule_iso_resource ( r ) ;
spin_unlock_irq ( & client - > lock ) ;
}
2009-01-04 16:23:29 +01:00
static int init_iso_resource ( struct client * client ,
struct fw_cdev_allocate_iso_resource * request , int todo )
2009-01-04 16:23:29 +01:00
{
struct iso_resource_event * e1 , * e2 ;
struct iso_resource * r ;
int ret ;
if ( ( request - > channels = = 0 & & request - > bandwidth = = 0 ) | |
request - > bandwidth > BANDWIDTH_AVAILABLE_INITIAL | |
request - > bandwidth < 0 )
return - EINVAL ;
r = kmalloc ( sizeof ( * r ) , GFP_KERNEL ) ;
e1 = kmalloc ( sizeof ( * e1 ) , GFP_KERNEL ) ;
e2 = kmalloc ( sizeof ( * e2 ) , GFP_KERNEL ) ;
if ( r = = NULL | | e1 = = NULL | | e2 = = NULL ) {
ret = - ENOMEM ;
goto fail ;
}
INIT_DELAYED_WORK ( & r - > work , iso_resource_work ) ;
r - > client = client ;
2009-01-04 16:23:29 +01:00
r - > todo = todo ;
2009-01-04 16:23:29 +01:00
r - > generation = - 1 ;
r - > channels = request - > channels ;
r - > bandwidth = request - > bandwidth ;
r - > e_alloc = e1 ;
r - > e_dealloc = e2 ;
e1 - > resource . closure = request - > closure ;
e1 - > resource . type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED ;
e2 - > resource . closure = request - > closure ;
e2 - > resource . type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED ;
2009-01-04 16:23:29 +01:00
if ( todo = = ISO_RES_ALLOC ) {
r - > resource . release = release_iso_resource ;
ret = add_client_resource ( client , & r - > resource , GFP_KERNEL ) ;
2009-01-11 13:44:46 +01:00
if ( ret < 0 )
goto fail ;
2009-01-04 16:23:29 +01:00
} else {
r - > resource . release = NULL ;
r - > resource . handle = - 1 ;
2009-01-11 13:44:46 +01:00
schedule_iso_resource ( r ) ;
2009-01-04 16:23:29 +01:00
}
2009-01-04 16:23:29 +01:00
request - > handle = r - > resource . handle ;
return 0 ;
fail :
kfree ( r ) ;
kfree ( e1 ) ;
kfree ( e2 ) ;
return ret ;
}
2009-01-04 16:23:29 +01:00
static int ioctl_allocate_iso_resource ( struct client * client , void * buffer )
{
struct fw_cdev_allocate_iso_resource * request = buffer ;
return init_iso_resource ( client , request , ISO_RES_ALLOC ) ;
}
2009-01-04 16:23:29 +01:00
static int ioctl_deallocate_iso_resource ( struct client * client , void * buffer )
{
struct fw_cdev_deallocate * request = buffer ;
return release_client_resource ( client , request - > handle ,
release_iso_resource , NULL ) ;
}
2009-01-04 16:23:29 +01:00
static int ioctl_allocate_iso_resource_once ( struct client * client , void * buffer )
{
struct fw_cdev_allocate_iso_resource * request = buffer ;
return init_iso_resource ( client , request , ISO_RES_ALLOC_ONCE ) ;
}
static int ioctl_deallocate_iso_resource_once ( struct client * client , void * buffer )
{
struct fw_cdev_allocate_iso_resource * request = buffer ;
return init_iso_resource ( client , request , ISO_RES_DEALLOC_ONCE ) ;
}
2009-03-10 20:59:16 +01:00
/*
* Returns a speed code : Maximum speed to or from this device ,
* limited by the device ' s link speed , the local node ' s link speed ,
* and all PHY port speeds between the two links .
*/
2009-01-04 16:23:29 +01:00
static int ioctl_get_speed ( struct client * client , void * buffer )
{
2009-03-10 20:59:16 +01:00
return client - > device - > max_speed ;
2009-01-04 16:23:29 +01:00
}
2009-01-04 16:23:29 +01:00
static int ioctl_send_broadcast_request ( struct client * client , void * buffer )
{
struct fw_cdev_send_request * request = buffer ;
switch ( request - > tcode ) {
case TCODE_WRITE_QUADLET_REQUEST :
case TCODE_WRITE_BLOCK_REQUEST :
break ;
default :
return - EINVAL ;
}
2009-01-04 16:23:29 +01:00
/* Security policy: Only allow accesses to Units Space. */
if ( request - > offset < CSR_REGISTER_BASE + CSR_CONFIG_ROM_END )
return - EACCES ;
2009-01-04 16:23:29 +01:00
return init_request ( client , request , LOCAL_BUS | 0x3f , SCODE_100 ) ;
}
2009-03-05 19:08:40 +01:00
static int ioctl_send_stream_packet ( struct client * client , void * buffer )
{
2009-03-10 21:02:21 +01:00
struct fw_cdev_send_stream_packet * p = buffer ;
struct fw_cdev_send_request request ;
int dest ;
2009-03-05 19:08:40 +01:00
2009-03-10 21:02:21 +01:00
if ( p - > speed > client - > device - > card - > link_speed | |
p - > length > 1024 < < p - > speed )
return - EIO ;
2009-03-05 19:08:40 +01:00
2009-03-10 21:02:21 +01:00
if ( p - > tag > 3 | | p - > channel > 63 | | p - > sy > 15 )
return - EINVAL ;
dest = fw_stream_packet_destination_id ( p - > tag , p - > channel , p - > sy ) ;
request . tcode = TCODE_STREAM_DATA ;
request . length = p - > length ;
request . closure = p - > closure ;
request . data = p - > data ;
request . generation = p - > generation ;
return init_request ( client , & request , dest , p - > speed ) ;
2009-03-05 19:08:40 +01:00
}
2007-04-30 15:03:13 -04:00
static int ( * const ioctl_handlers [ ] ) ( struct client * client , void * buffer ) = {
ioctl_get_info ,
ioctl_send_request ,
ioctl_allocate ,
ioctl_deallocate ,
ioctl_send_response ,
ioctl_initiate_bus_reset ,
ioctl_add_descriptor ,
ioctl_remove_descriptor ,
ioctl_create_iso_context ,
ioctl_queue_iso ,
ioctl_start_iso ,
ioctl_stop_iso ,
2007-09-29 10:41:58 +02:00
ioctl_get_cycle_timer ,
2009-01-04 16:23:29 +01:00
ioctl_allocate_iso_resource ,
ioctl_deallocate_iso_resource ,
2009-01-04 16:23:29 +01:00
ioctl_allocate_iso_resource_once ,
ioctl_deallocate_iso_resource_once ,
2009-01-04 16:23:29 +01:00
ioctl_get_speed ,
2009-01-04 16:23:29 +01:00
ioctl_send_broadcast_request ,
2009-03-05 19:08:40 +01:00
ioctl_send_stream_packet ,
2007-04-30 15:03:13 -04:00
} ;
2008-12-14 21:47:04 +01:00
static int dispatch_ioctl ( struct client * client ,
unsigned int cmd , void __user * arg )
2006-12-19 19:58:31 -05:00
{
2007-04-30 15:03:13 -04:00
char buffer [ 256 ] ;
2008-12-14 21:45:45 +01:00
int ret ;
2007-04-30 15:03:13 -04:00
if ( _IOC_TYPE ( cmd ) ! = ' # ' | |
_IOC_NR ( cmd ) > = ARRAY_SIZE ( ioctl_handlers ) )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
2007-04-30 15:03:13 -04:00
if ( _IOC_DIR ( cmd ) & _IOC_WRITE ) {
2007-05-09 19:23:14 -04:00
if ( _IOC_SIZE ( cmd ) > sizeof ( buffer ) | |
2007-04-30 15:03:13 -04:00
copy_from_user ( buffer , arg , _IOC_SIZE ( cmd ) ) )
return - EFAULT ;
}
2008-12-14 21:45:45 +01:00
ret = ioctl_handlers [ _IOC_NR ( cmd ) ] ( client , buffer ) ;
if ( ret < 0 )
return ret ;
2007-04-30 15:03:13 -04:00
if ( _IOC_DIR ( cmd ) & _IOC_READ ) {
2007-05-09 19:23:14 -04:00
if ( _IOC_SIZE ( cmd ) > sizeof ( buffer ) | |
2007-04-30 15:03:13 -04:00
copy_to_user ( arg , buffer , _IOC_SIZE ( cmd ) ) )
return - EFAULT ;
2006-12-19 19:58:31 -05:00
}
2007-04-30 15:03:13 -04:00
2008-12-14 21:45:45 +01:00
return ret ;
2006-12-19 19:58:31 -05:00
}
2008-12-14 21:47:04 +01:00
static long fw_device_op_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
2006-12-19 19:58:31 -05:00
{
struct client * client = file - > private_data ;
2008-05-16 11:15:23 -04:00
if ( fw_device_is_shutdown ( client - > device ) )
return - ENODEV ;
2006-12-19 19:58:31 -05:00
return dispatch_ioctl ( client , cmd , ( void __user * ) arg ) ;
}
# ifdef CONFIG_COMPAT
2008-12-14 21:47:04 +01:00
static long fw_device_op_compat_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
2006-12-19 19:58:31 -05:00
{
struct client * client = file - > private_data ;
2008-05-16 11:15:23 -04:00
if ( fw_device_is_shutdown ( client - > device ) )
return - ENODEV ;
2006-12-19 19:58:31 -05:00
return dispatch_ioctl ( client , cmd , compat_ptr ( arg ) ) ;
}
# endif
static int fw_device_op_mmap ( struct file * file , struct vm_area_struct * vma )
{
struct client * client = file - > private_data ;
2007-02-16 17:34:38 -05:00
enum dma_data_direction direction ;
unsigned long size ;
2008-12-14 21:45:45 +01:00
int page_count , ret ;
2007-02-16 17:34:38 -05:00
2008-05-16 11:15:23 -04:00
if ( fw_device_is_shutdown ( client - > device ) )
return - ENODEV ;
2007-02-16 17:34:38 -05:00
/* FIXME: We could support multiple buffers, but we don't. */
if ( client - > buffer . pages ! = NULL )
return - EBUSY ;
if ( ! ( vma - > vm_flags & VM_SHARED ) )
return - EINVAL ;
2006-12-19 19:58:31 -05:00
2007-02-16 17:34:38 -05:00
if ( vma - > vm_start & ~ PAGE_MASK )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
client - > vm_start = vma - > vm_start ;
2007-02-16 17:34:38 -05:00
size = vma - > vm_end - vma - > vm_start ;
page_count = size > > PAGE_SHIFT ;
if ( size & ~ PAGE_MASK )
return - EINVAL ;
if ( vma - > vm_flags & VM_WRITE )
direction = DMA_TO_DEVICE ;
else
direction = DMA_FROM_DEVICE ;
2008-12-14 21:45:45 +01:00
ret = fw_iso_buffer_init ( & client - > buffer , client - > device - > card ,
page_count , direction ) ;
if ( ret < 0 )
return ret ;
2006-12-19 19:58:31 -05:00
2008-12-14 21:45:45 +01:00
ret = fw_iso_buffer_map ( & client - > buffer , vma ) ;
if ( ret < 0 )
2007-02-16 17:34:38 -05:00
fw_iso_buffer_destroy ( & client - > buffer , client - > device - > card ) ;
2008-12-14 21:45:45 +01:00
return ret ;
2006-12-19 19:58:31 -05:00
}
2008-12-21 16:47:17 +01:00
static int shutdown_resource ( int id , void * p , void * data )
{
struct client_resource * r = p ;
struct client * client = data ;
r - > release ( client , r ) ;
2009-01-04 16:23:29 +01:00
client_put ( client ) ;
2008-12-21 16:47:17 +01:00
return 0 ;
}
2006-12-19 19:58:31 -05:00
static int fw_device_op_release ( struct inode * inode , struct file * file )
{
struct client * client = file - > private_data ;
2007-03-07 12:12:48 -05:00
struct event * e , * next_e ;
2006-12-19 19:58:31 -05:00
2008-12-14 19:19:23 +01:00
mutex_lock ( & client - > device - > client_list_mutex ) ;
list_del ( & client - > link ) ;
mutex_unlock ( & client - > device - > client_list_mutex ) ;
2006-12-19 19:58:31 -05:00
if ( client - > iso_context )
fw_iso_context_destroy ( client - > iso_context ) ;
2009-01-05 20:28:10 +01:00
if ( client - > buffer . pages )
fw_iso_buffer_destroy ( & client - > buffer , client - > device - > card ) ;
2008-12-21 16:47:17 +01:00
/* Freeze client->resource_idr and client->event_list */
2009-01-04 16:23:29 +01:00
spin_lock_irq ( & client - > lock ) ;
2008-12-21 16:47:17 +01:00
client - > in_shutdown = true ;
2009-01-04 16:23:29 +01:00
spin_unlock_irq ( & client - > lock ) ;
2007-03-28 21:26:42 +02:00
2008-12-21 16:47:17 +01:00
idr_for_each ( & client - > resource_idr , shutdown_resource , client ) ;
idr_remove_all ( & client - > resource_idr ) ;
idr_destroy ( & client - > resource_idr ) ;
2007-03-07 12:12:50 -05:00
2007-03-07 12:12:48 -05:00
list_for_each_entry_safe ( e , next_e , & client - > event_list , link )
kfree ( e ) ;
2006-12-19 19:58:31 -05:00
2009-01-04 16:23:29 +01:00
client_put ( client ) ;
2006-12-19 19:58:31 -05:00
return 0 ;
}
static unsigned int fw_device_op_poll ( struct file * file , poll_table * pt )
{
struct client * client = file - > private_data ;
2007-03-07 12:12:48 -05:00
unsigned int mask = 0 ;
2006-12-19 19:58:31 -05:00
poll_wait ( file , & client - > wait , pt ) ;
2007-03-07 12:12:48 -05:00
if ( fw_device_is_shutdown ( client - > device ) )
mask | = POLLHUP | POLLERR ;
2006-12-19 19:58:31 -05:00
if ( ! list_empty ( & client - > event_list ) )
2007-03-07 12:12:48 -05:00
mask | = POLLIN | POLLRDNORM ;
return mask ;
2006-12-19 19:58:31 -05:00
}
2007-01-14 15:29:07 +01:00
const struct file_operations fw_device_ops = {
2006-12-19 19:58:31 -05:00
. owner = THIS_MODULE ,
. open = fw_device_op_open ,
. read = fw_device_op_read ,
. unlocked_ioctl = fw_device_op_ioctl ,
. poll = fw_device_op_poll ,
. release = fw_device_op_release ,
. mmap = fw_device_op_mmap ,
# ifdef CONFIG_COMPAT
2007-01-21 20:45:32 +01:00
. compat_ioctl = fw_device_op_compat_ioctl ,
2006-12-19 19:58:31 -05:00
# endif
} ;