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 .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/wait.h>
# include <linux/errno.h>
# include <linux/device.h>
# include <linux/vmalloc.h>
# include <linux/poll.h>
2007-09-29 10:41:58 +02:00
# include <linux/preempt.h>
# include <linux/time.h>
2006-12-19 19:58:31 -05:00
# include <linux/delay.h>
# include <linux/mm.h>
2007-03-07 12:12:44 -05:00
# include <linux/idr.h>
2006-12-19 19:58:31 -05:00
# include <linux/compat.h>
2007-04-30 15:03:15 -04:00
# include <linux/firewire-cdev.h>
2007-09-29 10:41:58 +02:00
# include <asm/system.h>
2006-12-19 19:58:31 -05:00
# include <asm/uaccess.h>
# include "fw-transaction.h"
# include "fw-topology.h"
# include "fw-device.h"
2007-03-27 01:43:41 -04:00
struct client ;
struct client_resource {
struct list_head link ;
void ( * release ) ( struct client * client , struct client_resource * r ) ;
u32 handle ;
} ;
2007-05-07 20:33:32 -04:00
/*
* dequeue_event ( ) just kfree ( ) ' s the event , so the event has to be
* the first field in the struct .
*/
2006-12-19 19:58:31 -05:00
struct event {
struct { void * data ; size_t size ; } v [ 2 ] ;
struct list_head link ;
} ;
2007-03-07 12:12:41 -05:00
struct bus_reset {
struct event event ;
struct fw_cdev_event_bus_reset reset ;
} ;
2006-12-19 19:58:31 -05:00
struct response {
struct event event ;
struct fw_transaction transaction ;
struct client * client ;
2007-03-27 01:43:41 -04:00
struct client_resource resource ;
2006-12-19 19:58:31 -05:00
struct fw_cdev_event_response response ;
} ;
struct iso_interrupt {
struct event event ;
struct fw_cdev_event_iso_interrupt interrupt ;
} ;
struct client {
2007-03-07 12:12:43 -05:00
u32 version ;
2006-12-19 19:58:31 -05:00
struct fw_device * device ;
spinlock_t lock ;
2007-03-28 21:26:42 +02:00
u32 resource_handle ;
2007-03-27 01:43:41 -04:00
struct list_head resource_list ;
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 ;
2006-12-19 19:58:31 -05:00
} ;
static inline void __user *
u64_to_uptr ( __u64 value )
{
return ( void __user * ) ( unsigned long ) value ;
}
static inline __u64
uptr_to_u64 ( void __user * ptr )
{
return ( __u64 ) ( unsigned long ) ptr ;
}
static int fw_device_op_open ( struct inode * inode , struct file * file )
{
struct fw_device * device ;
struct client * client ;
2007-03-07 12:12:41 -05:00
unsigned long flags ;
2006-12-19 19:58:31 -05:00
2007-03-07 12:12:44 -05:00
device = fw_device_from_devt ( inode - > i_rdev ) ;
if ( device = = NULL )
return - ENODEV ;
2006-12-19 19:58:31 -05:00
2007-05-09 19:23:14 -04:00
client = kzalloc ( sizeof ( * client ) , GFP_KERNEL ) ;
2006-12-19 19:58:31 -05:00
if ( client = = NULL )
return - ENOMEM ;
client - > device = fw_device_get ( device ) ;
INIT_LIST_HEAD ( & client - > event_list ) ;
2007-03-27 01:43:41 -04:00
INIT_LIST_HEAD ( & client - > resource_list ) ;
2006-12-19 19:58:31 -05:00
spin_lock_init ( & client - > lock ) ;
init_waitqueue_head ( & client - > wait ) ;
file - > private_data = client ;
2007-03-07 12:12:41 -05:00
spin_lock_irqsave ( & device - > card - > lock , flags ) ;
list_add_tail ( & client - > link , & device - > client_list ) ;
spin_unlock_irqrestore ( & device - > card - > lock , flags ) ;
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 ) ;
list_add_tail ( & event - > link , & client - > event_list ) ;
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
}
2007-03-07 12:12:48 -05:00
static int
dequeue_event ( struct client * client , char __user * buffer , size_t count )
2006-12-19 19:58:31 -05:00
{
unsigned long flags ;
struct event * event ;
size_t size , total ;
2007-03-07 12:12:48 -05:00
int i , retval ;
2006-12-19 19:58:31 -05:00
2007-03-07 12:12:48 -05:00
retval = wait_event_interruptible ( client - > wait ,
! list_empty ( & client - > event_list ) | |
fw_device_is_shutdown ( client - > device ) ) ;
if ( retval < 0 )
return retval ;
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
2007-03-07 12:12:48 -05:00
spin_lock_irqsave ( & client - > lock , flags ) ;
2006-12-19 19:58:31 -05:00
event = container_of ( client - > event_list . next , struct event , link ) ;
list_del ( & event - > link ) ;
spin_unlock_irqrestore ( & client - > lock , flags ) ;
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 ) ) {
retval = - 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 ;
}
retval = total ;
out :
kfree ( event ) ;
return retval ;
}
static ssize_t
fw_device_op_read ( struct file * file ,
char __user * buffer , size_t count , loff_t * offset )
{
struct client * client = file - > private_data ;
return dequeue_event ( client , buffer , count ) ;
}
2007-03-07 12:12:43 -05:00
static void
fill_bus_reset_event ( struct fw_cdev_event_bus_reset * event ,
2007-03-27 01:43:39 -04:00
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 ;
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 ;
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 ;
event - > generation = card - > generation ;
}
2007-03-07 12:12:48 -05:00
static void
for_each_client ( struct fw_device * device ,
void ( * callback ) ( struct client * client ) )
{
struct fw_card * card = device - > card ;
struct client * c ;
unsigned long flags ;
spin_lock_irqsave ( & card - > lock , flags ) ;
list_for_each_entry ( c , & device - > client_list , link )
callback ( c ) ;
spin_unlock_irqrestore ( & card - > lock , flags ) ;
}
2007-03-07 12:12:41 -05:00
static void
queue_bus_reset_event ( struct client * client )
{
struct bus_reset * bus_reset ;
2007-05-09 19:23:14 -04:00
bus_reset = kzalloc ( sizeof ( * bus_reset ) , GFP_ATOMIC ) ;
2007-03-07 12:12:41 -05:00
if ( bus_reset = = NULL ) {
fw_notify ( " Out of memory when allocating bus reset event \n " ) ;
return ;
}
2007-03-27 01:43:39 -04:00
fill_bus_reset_event ( & bus_reset - > reset , client ) ;
2007-03-07 12:12:41 -05:00
queue_event ( client , & bus_reset - > event ,
2007-05-09 19:23:14 -04:00
& bus_reset - > reset , sizeof ( bus_reset - > reset ) , NULL , 0 ) ;
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 ;
2007-04-30 15:03:13 -04:00
client - > version = get_info - > version ;
get_info - > version = FW_CDEV_VERSION ;
2007-03-07 12:12:43 -05:00
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
2007-03-20 19:42:15 +01:00
if ( copy_to_user ( uptr , client - > device - > config_rom ,
min ( want , have ) ) )
2007-03-07 12:12:43 -05:00
return - EFAULT ;
}
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
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
2007-04-30 15:03:13 -04:00
get_info - > card = client - > device - > card - > index ;
2006-12-19 19:58:31 -05:00
return 0 ;
}
2007-03-27 01:43:41 -04:00
static void
add_client_resource ( struct client * client , struct client_resource * resource )
{
unsigned long flags ;
spin_lock_irqsave ( & client - > lock , flags ) ;
list_add_tail ( & resource - > link , & client - > resource_list ) ;
resource - > handle = client - > resource_handle + + ;
spin_unlock_irqrestore ( & client - > lock , flags ) ;
}
static int
release_client_resource ( struct client * client , u32 handle ,
struct client_resource * * resource )
{
struct client_resource * r ;
unsigned long flags ;
spin_lock_irqsave ( & client - > lock , flags ) ;
list_for_each_entry ( r , & client - > resource_list , link ) {
if ( r - > handle = = handle ) {
list_del ( & r - > link ) ;
break ;
}
}
spin_unlock_irqrestore ( & client - > lock , flags ) ;
if ( & r - > link = = & client - > resource_list )
return - EINVAL ;
if ( resource )
* resource = r ;
else
r - > release ( client , r ) ;
return 0 ;
}
static void
release_transaction ( struct client * client , struct client_resource * resource )
{
struct response * response =
container_of ( resource , struct response , resource ) ;
fw_cancel_transaction ( client - > device - > card , & response - > transaction ) ;
}
2006-12-19 19:58:31 -05:00
static void
complete_transaction ( struct fw_card * card , int rcode ,
void * payload , size_t length , void * data )
{
struct response * response = data ;
struct client * client = response - > client ;
2007-03-07 12:12:50 -05:00
unsigned long flags ;
2006-12-19 19:58:31 -05:00
if ( length < response - > response . length )
response - > response . length = length ;
if ( rcode = = RCODE_COMPLETE )
memcpy ( response - > response . data , payload ,
response - > response . length ) ;
2007-03-07 12:12:50 -05:00
spin_lock_irqsave ( & client - > lock , flags ) ;
2007-03-27 01:43:41 -04:00
list_del ( & response - > resource . link ) ;
2007-03-07 12:12:50 -05:00
spin_unlock_irqrestore ( & client - > lock , flags ) ;
2006-12-19 19:58:31 -05:00
response - > response . type = FW_CDEV_EVENT_RESPONSE ;
response - > response . rcode = rcode ;
queue_event ( client , & response - > event ,
2007-05-09 19:23:14 -04:00
& response - > response , sizeof ( response - > response ) ,
2006-12-19 19:58:31 -05:00
response - > response . data , response - > response . length ) ;
}
2007-05-27 07:09:18 -04:00
static int ioctl_send_request ( struct client * client , void * buffer )
2006-12-19 19:58:31 -05:00
{
struct fw_device * device = client - > device ;
2007-04-30 15:03:13 -04:00
struct fw_cdev_send_request * request = buffer ;
2006-12-19 19:58:31 -05:00
struct response * response ;
/* What is the biggest size we'll accept, really? */
2007-04-30 15:03:13 -04:00
if ( request - > length > 4096 )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
2007-05-09 19:23:14 -04:00
response = kmalloc ( sizeof ( * response ) + request - > length , GFP_KERNEL ) ;
2006-12-19 19:58:31 -05:00
if ( response = = NULL )
return - ENOMEM ;
response - > client = client ;
2007-04-30 15:03:13 -04:00
response - > response . length = request - > length ;
response - > response . closure = request - > closure ;
2006-12-19 19:58:31 -05:00
2007-04-30 15:03:13 -04:00
if ( request - > data & &
2006-12-19 19:58:31 -05:00
copy_from_user ( response - > response . data ,
2007-04-30 15:03:13 -04:00
u64_to_uptr ( request - > data ) , request - > length ) ) {
2006-12-19 19:58:31 -05:00
kfree ( response ) ;
return - EFAULT ;
}
2007-03-27 01:43:41 -04:00
response - > resource . release = release_transaction ;
add_client_resource ( client , & response - > resource ) ;
2007-03-07 12:12:50 -05:00
2006-12-19 19:58:31 -05:00
fw_send_request ( device - > card , & response - > transaction ,
2007-04-30 15:03:13 -04:00
request - > tcode & 0x1f ,
2007-01-23 21:11:43 +01:00
device - > node - > node_id ,
2007-04-30 15:03:13 -04:00
request - > generation ,
2007-06-10 21:31:36 +02:00
device - > max_speed ,
2007-04-30 15:03:13 -04:00
request - > offset ,
response - > response . data , request - > length ,
2006-12-19 19:58:31 -05:00
complete_transaction , response ) ;
2007-04-30 15:03:13 -04:00
if ( request - > data )
2007-05-09 19:23:14 -04:00
return sizeof ( request ) + request - > length ;
2006-12-19 19:58:31 -05:00
else
2007-05-09 19:23:14 -04:00
return sizeof ( request ) ;
2006-12-19 19:58:31 -05:00
}
struct address_handler {
struct fw_address_handler handler ;
__u64 closure ;
struct client * client ;
2007-03-27 01:43:41 -04:00
struct client_resource resource ;
2006-12-19 19:58:31 -05:00
} ;
struct request {
struct fw_request * request ;
void * data ;
size_t length ;
2007-03-27 01:43:41 -04:00
struct client_resource resource ;
2006-12-19 19:58:31 -05:00
} ;
struct request_event {
struct event event ;
struct fw_cdev_event_request request ;
} ;
2007-03-27 01:43:41 -04:00
static void
release_request ( struct client * client , struct client_resource * resource )
{
struct request * request =
container_of ( resource , struct request , resource ) ;
fw_send_response ( client - > device - > card , request - > request ,
RCODE_CONFLICT_ERROR ) ;
kfree ( request ) ;
}
2006-12-19 19:58:31 -05:00
static void
handle_request ( struct fw_card * card , struct fw_request * r ,
int tcode , int destination , int source ,
int generation , int speed ,
unsigned long long offset ,
void * payload , size_t length , void * callback_data )
{
struct address_handler * handler = callback_data ;
struct request * request ;
struct request_event * e ;
struct client * client = handler - > client ;
2007-05-09 19:23:14 -04:00
request = kmalloc ( sizeof ( * request ) , GFP_ATOMIC ) ;
e = kmalloc ( sizeof ( * e ) , GFP_ATOMIC ) ;
2006-12-19 19:58:31 -05:00
if ( request = = NULL | | e = = NULL ) {
kfree ( request ) ;
kfree ( e ) ;
fw_send_response ( card , r , RCODE_CONFLICT_ERROR ) ;
return ;
}
request - > request = r ;
request - > data = payload ;
request - > length = length ;
2007-03-27 01:43:41 -04:00
request - > resource . release = release_request ;
add_client_resource ( client , & request - > resource ) ;
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 ;
2007-03-27 01:43:41 -04:00
e - > request . handle = request - > resource . handle ;
2006-12-19 19:58:31 -05:00
e - > request . closure = handler - > closure ;
queue_event ( client , & e - > event ,
2007-05-09 19:23:14 -04:00
& e - > request , sizeof ( e - > request ) , payload , length ) ;
2006-12-19 19:58:31 -05:00
}
2007-03-27 01:43:41 -04:00
static void
release_address_handler ( struct client * client ,
struct client_resource * resource )
{
struct address_handler * handler =
container_of ( resource , struct address_handler , resource ) ;
fw_core_remove_address_handler ( & handler - > handler ) ;
kfree ( handler ) ;
}
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 ;
2006-12-19 19:58:31 -05:00
struct address_handler * handler ;
struct fw_address_region region ;
2007-05-09 19:23:14 -04:00
handler = kmalloc ( sizeof ( * handler ) , GFP_KERNEL ) ;
2006-12-19 19:58:31 -05:00
if ( handler = = NULL )
return - ENOMEM ;
2007-04-30 15:03:13 -04:00
region . start = request - > offset ;
region . end = request - > offset + request - > length ;
handler - > handler . length = request - > length ;
2006-12-19 19:58:31 -05:00
handler - > handler . address_callback = handle_request ;
handler - > handler . callback_data = handler ;
2007-04-30 15:03:13 -04:00
handler - > closure = request - > closure ;
2006-12-19 19:58:31 -05:00
handler - > client = client ;
if ( fw_core_add_address_handler ( & handler - > handler , & region ) < 0 ) {
kfree ( handler ) ;
return - EBUSY ;
}
2007-03-27 01:43:41 -04:00
handler - > resource . release = release_address_handler ;
add_client_resource ( client , & handler - > resource ) ;
2007-04-30 15:03:13 -04:00
request - > handle = handler - > 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
2007-04-30 15:03:13 -04:00
return release_client_resource ( client , request - > handle , 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 ;
2006-12-19 19:58:31 -05:00
struct request * r ;
2007-04-30 15:03:13 -04:00
if ( release_client_resource ( client , request - > handle , & resource ) < 0 )
2006-12-19 19:58:31 -05:00
return - EINVAL ;
2007-03-27 01:43:41 -04:00
r = container_of ( resource , struct request , 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-28 21:26:42 +02:00
struct descriptor {
struct fw_descriptor d ;
2007-03-27 01:43:41 -04:00
struct client_resource resource ;
2007-03-28 21:26:42 +02:00
u32 data [ 0 ] ;
} ;
2007-03-27 01:43:41 -04:00
static void release_descriptor ( struct client * client ,
struct client_resource * resource )
{
struct descriptor * descriptor =
container_of ( resource , struct descriptor , resource ) ;
fw_core_remove_descriptor ( & descriptor - > d ) ;
kfree ( descriptor ) ;
}
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 ;
2007-03-28 21:26:42 +02:00
struct descriptor * descriptor ;
int retval ;
2007-04-30 15:03:13 -04:00
if ( request - > length > 256 )
2007-03-28 21:26:42 +02:00
return - EINVAL ;
descriptor =
2007-05-09 19:23:14 -04:00
kmalloc ( sizeof ( * descriptor ) + request - > length * 4 , GFP_KERNEL ) ;
2007-03-28 21:26:42 +02:00
if ( descriptor = = NULL )
return - ENOMEM ;
if ( copy_from_user ( descriptor - > data ,
2007-04-30 15:03:13 -04:00
u64_to_uptr ( request - > data ) , request - > length * 4 ) ) {
2007-03-28 21:26:42 +02:00
kfree ( descriptor ) ;
return - EFAULT ;
}
2007-04-30 15:03:13 -04:00
descriptor - > d . length = request - > length ;
descriptor - > d . immediate = request - > immediate ;
descriptor - > d . key = request - > key ;
2007-03-28 21:26:42 +02:00
descriptor - > d . data = descriptor - > data ;
retval = fw_core_add_descriptor ( & descriptor - > d ) ;
if ( retval < 0 ) {
kfree ( descriptor ) ;
return retval ;
}
2007-03-27 01:43:41 -04:00
descriptor - > resource . release = release_descriptor ;
add_client_resource ( client , & descriptor - > resource ) ;
2007-04-30 15:03:13 -04:00
request - > handle = descriptor - > resource . handle ;
2007-03-28 21:26:42 +02:00
return 0 ;
}
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
2007-04-30 15:03:13 -04:00
return release_client_resource ( client , request - > handle , NULL ) ;
2007-03-28 21:26:42 +02:00
}
2006-12-19 19:58:31 -05:00
static void
2007-02-16 17:34:44 -05:00
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 ;
2007-08-03 20:56:31 +02:00
struct iso_interrupt * irq ;
2006-12-19 19:58:31 -05:00
2007-08-03 20:56:31 +02:00
irq = kzalloc ( sizeof ( * irq ) + header_length , GFP_ATOMIC ) ;
if ( irq = = NULL )
2006-12-19 19:58:31 -05:00
return ;
2007-08-03 20:56:31 +02:00
irq - > interrupt . type = FW_CDEV_EVENT_ISO_INTERRUPT ;
irq - > interrupt . closure = client - > iso_closure ;
irq - > interrupt . cycle = cycle ;
irq - > interrupt . header_length = header_length ;
memcpy ( irq - > interrupt . header , header , header_length ) ;
queue_event ( client , & irq - > event , & irq - > interrupt ,
sizeof ( irq - > 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
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)
# define GET_TAG(v) (((v) >> 18) & 0x02)
# define GET_SY(v) (((v) >> 20) & 0x04)
# 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
2007-04-30 15:03:14 -04:00
if ( request - > handle ! = 0 )
return - EINVAL ;
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 ;
if ( request - > handle ! = 0 )
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 ;
}
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 ,
2007-04-30 15:03:13 -04:00
} ;
2006-12-19 19:58:31 -05:00
static int
dispatch_ioctl ( struct client * client , unsigned int cmd , void __user * arg )
{
2007-04-30 15:03:13 -04:00
char buffer [ 256 ] ;
int retval ;
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 ;
}
retval = ioctl_handlers [ _IOC_NR ( cmd ) ] ( client , buffer ) ;
if ( retval < 0 )
return retval ;
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
return 0 ;
2006-12-19 19:58:31 -05:00
}
static long
fw_device_op_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
{
struct client * client = file - > private_data ;
return dispatch_ioctl ( client , cmd , ( void __user * ) arg ) ;
}
# ifdef CONFIG_COMPAT
static long
fw_device_op_compat_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
{
struct client * client = file - > private_data ;
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 ;
int page_count , retval ;
/* 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 ;
retval = fw_iso_buffer_init ( & client - > buffer , client - > device - > card ,
page_count , direction ) ;
if ( retval < 0 )
return retval ;
2006-12-19 19:58:31 -05:00
2007-02-16 17:34:38 -05:00
retval = fw_iso_buffer_map ( & client - > buffer , vma ) ;
if ( retval < 0 )
fw_iso_buffer_destroy ( & client - > buffer , client - > device - > card ) ;
return retval ;
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 ;
2007-03-27 01:43:41 -04:00
struct client_resource * r , * next_r ;
2007-03-07 12:12:41 -05:00
unsigned long flags ;
2006-12-19 19:58:31 -05:00
2007-02-16 17:34:38 -05:00
if ( client - > buffer . pages )
fw_iso_buffer_destroy ( & client - > buffer , client - > device - > card ) ;
2006-12-19 19:58:31 -05:00
if ( client - > iso_context )
fw_iso_context_destroy ( client - > iso_context ) ;
2007-03-27 01:43:41 -04:00
list_for_each_entry_safe ( r , next_r , & client - > resource_list , link )
r - > release ( client , r ) ;
2007-03-28 21:26:42 +02:00
2007-05-07 20:33:32 -04:00
/*
* FIXME : We should wait for the async tasklets to stop
* running before freeing the memory .
*/
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
2007-03-07 12:12:41 -05:00
spin_lock_irqsave ( & client - > device - > card - > lock , flags ) ;
list_del ( & client - > link ) ;
spin_unlock_irqrestore ( & client - > device - > card - > lock , flags ) ;
2006-12-19 19:58:31 -05:00
fw_device_put ( client - > device ) ;
kfree ( client ) ;
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
} ;