Newer
Older
/*
* Char device for device raw access
* Copyright (C) 2005-2007 Kristian Hoegsberg <krh@bitplanet.net>
*
* 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.
*/
Stefan Richter
committed
#include <linux/bug.h>
#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>
#include <linux/irqflags.h>
Jay Fenlason, Stefan Richter
committed
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/sched.h> /* required for linux/wait.h */
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
Jay Fenlason, Stefan Richter
committed
#include <linux/workqueue.h>
#include <asm/system.h>
/*
* ABI version history is documented in linux/firewire-cdev.h.
*/
#define FW_CDEV_KERNEL_VERSION 4
#define FW_CDEV_VERSION_EVENT_REQUEST2 4
#define FW_CDEV_VERSION_ALLOCATE_REGION_END 4
struct fw_device *device;
bool in_shutdown;
struct idr resource_idr;
struct list_head event_list;
wait_queue_head_t wait;
wait_queue_head_t tx_flush_wait;
u64 bus_reset_closure;
Kristian Høgsberg
committed
struct fw_iso_context *iso_context;
Kristian Høgsberg
committed
u64 iso_closure;
Kristian Høgsberg
committed
struct fw_iso_buffer buffer;
unsigned long vm_start;
struct list_head phy_receiver_link;
u64 phy_receiver_closure;
struct list_head link;
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);
}
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_card *card;
struct fw_request *request;
void *data;
size_t length;
};
struct descriptor_resource {
struct client_resource resource;
struct fw_descriptor descriptor;
u32 data[0];
};
Jay Fenlason, Stefan Richter
committed
struct iso_resource {
struct client_resource resource;
struct client *client;
/* Schedule work and access todo only with client->lock held. */
struct delayed_work work;
enum {ISO_RES_ALLOC, ISO_RES_REALLOC, ISO_RES_DEALLOC,
ISO_RES_ALLOC_ONCE, ISO_RES_DEALLOC_ONCE,} todo;
Jay Fenlason, Stefan Richter
committed
int generation;
u64 channels;
s32 bandwidth;
struct iso_resource_event *e_alloc, *e_dealloc;
};
static void release_iso_resource(struct client *, struct client_resource *);
static void schedule_iso_resource(struct iso_resource *r, unsigned long delay)
{
client_get(r->client);
if (!queue_delayed_work(fw_workqueue, &r->work, delay))
client_put(r->client);
}
static void schedule_if_iso_resource(struct client_resource *resource)
{
if (resource->release == release_iso_resource)
schedule_iso_resource(container_of(resource,
struct iso_resource, resource), 0);
}
/*
* 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;
Stefan Richter
committed
union {
struct fw_cdev_event_request request;
struct fw_cdev_event_request2 request2;
} req;
};
struct iso_interrupt_event {
struct event event;
struct fw_cdev_event_iso_interrupt interrupt;
};
struct iso_interrupt_mc_event {
struct event event;
struct fw_cdev_event_iso_interrupt_mc interrupt;
};
Jay Fenlason, Stefan Richter
committed
struct iso_resource_event {
struct event event;
struct fw_cdev_event_iso_resource iso_resource;
Jay Fenlason, Stefan Richter
committed
};
struct outbound_phy_packet_event {
struct event event;
struct client *client;
struct fw_packet p;
struct fw_cdev_event_phy_packet phy_packet;
};
struct inbound_phy_packet_event {
struct event event;
struct fw_cdev_event_phy_packet phy_packet;
};
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;
device = fw_device_get_by_devt(inode->i_rdev);
Kristian Høgsberg
committed
if (device == NULL)
return -ENODEV;
if (fw_device_is_shutdown(device)) {
fw_device_put(device);
return -ENODEV;
}
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (client == NULL) {
fw_device_put(device);
client->device = device;
spin_lock_init(&client->lock);
idr_init(&client->resource_idr);
INIT_LIST_HEAD(&client->event_list);
init_waitqueue_head(&client->wait);
init_waitqueue_head(&client->tx_flush_wait);
INIT_LIST_HEAD(&client->phy_receiver_link);
Stefan Richter
committed
INIT_LIST_HEAD(&client->link);
kref_init(&client->kref);
file->private_data = client;
return nonseekable_open(inode, file);
}
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);
if (client->in_shutdown)
kfree(event);
else
list_add_tail(&event->link, &client->event_list);
spin_unlock_irqrestore(&client->lock, flags);
wake_up_interruptible(&client->wait);
static int dequeue_event(struct client *client,
char __user *buffer, size_t count)
{
struct event *event;
size_t size, total;
ret = wait_event_interruptible(client->wait,
!list_empty(&client->event_list) ||
fw_device_is_shutdown(client->device));
if (ret < 0)
return ret;
Kristian Høgsberg
committed
if (list_empty(&client->event_list) &&
fw_device_is_shutdown(client->device))
return -ENODEV;
spin_lock_irq(&client->lock);
event = list_first_entry(&client->event_list, struct event, link);
list_del(&event->link);
spin_unlock_irq(&client->lock);
total = 0;
for (i = 0; i < ARRAY_SIZE(event->v) && total < count; i++) {
size = min(event->v[i].size, count - total);
Kristian Høgsberg
committed
if (copy_to_user(buffer + total, event->v[i].data, size)) {
Kristian Høgsberg
committed
}
total += size;
}
out:
kfree(event);
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);
}
static void fill_bus_reset_event(struct fw_cdev_event_bus_reset *event,
struct client *client)
struct fw_card *card = client->device->card;
spin_lock_irq(&card->lock);
event->closure = client->bus_reset_closure;
event->type = FW_CDEV_EVENT_BUS_RESET;
event->generation = client->device->generation;
event->node_id = client->device->node_id;
event->local_node_id = card->local_node->node_id;
event->bm_node_id = card->bm_node_id;
event->irm_node_id = card->irm_node->node_id;
event->root_node_id = card->root_node->node_id;
spin_unlock_irq(&card->lock);
static void for_each_client(struct fw_device *device,
void (*callback)(struct client *client))
Kristian Høgsberg
committed
{
struct client *c;
mutex_lock(&device->client_list_mutex);
Kristian Høgsberg
committed
list_for_each_entry(c, &device->client_list, link)
callback(c);
mutex_unlock(&device->client_list_mutex);
Kristian Høgsberg
committed
}
Jay Fenlason, Stefan Richter
committed
static int schedule_reallocations(int id, void *p, void *data)
{
schedule_if_iso_resource(p);
Jay Fenlason, Stefan Richter
committed
return 0;
}
static void queue_bus_reset_event(struct client *client)
struct bus_reset_event *e;
e = kzalloc(sizeof(*e), GFP_KERNEL);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
return;
}
fill_bus_reset_event(&e->reset, client);
queue_event(client, &e->event,
&e->reset, sizeof(e->reset), NULL, 0);
Jay Fenlason, Stefan Richter
committed
spin_lock_irq(&client->lock);
idr_for_each(&client->resource_idr, schedule_reallocations, client);
spin_unlock_irq(&client->lock);
}
void fw_device_cdev_update(struct fw_device *device)
{
Kristian Høgsberg
committed
for_each_client(device, queue_bus_reset_event);
}
Kristian Høgsberg
committed
static void wake_up_client(struct client *client)
{
wake_up_interruptible(&client->wait);
}
Kristian Høgsberg
committed
void fw_device_cdev_remove(struct fw_device *device)
{
for_each_client(device, wake_up_client);
union ioctl_arg {
struct fw_cdev_get_info get_info;
struct fw_cdev_send_request send_request;
struct fw_cdev_allocate allocate;
struct fw_cdev_deallocate deallocate;
struct fw_cdev_send_response send_response;
struct fw_cdev_initiate_bus_reset initiate_bus_reset;
struct fw_cdev_add_descriptor add_descriptor;
struct fw_cdev_remove_descriptor remove_descriptor;
struct fw_cdev_create_iso_context create_iso_context;
struct fw_cdev_queue_iso queue_iso;
struct fw_cdev_start_iso start_iso;
struct fw_cdev_stop_iso stop_iso;
struct fw_cdev_get_cycle_timer get_cycle_timer;
struct fw_cdev_allocate_iso_resource allocate_iso_resource;
struct fw_cdev_send_stream_packet send_stream_packet;
struct fw_cdev_get_cycle_timer2 get_cycle_timer2;
struct fw_cdev_send_phy_packet send_phy_packet;
struct fw_cdev_receive_phy_packets receive_phy_packets;
struct fw_cdev_set_iso_channels set_iso_channels;
};
static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
struct fw_cdev_get_info *a = &arg->get_info;
struct fw_cdev_event_bus_reset bus_reset;
unsigned long ret = 0;
client->version = a->version;
a->version = FW_CDEV_KERNEL_VERSION;
a->card = client->device->card->index;
down_read(&fw_device_rwsem);
if (a->rom != 0) {
size_t want = a->rom_length;
size_t have = client->device->config_rom_length * 4;
ret = copy_to_user(u64_to_uptr(a->rom),
client->device->config_rom, min(want, have));
a->rom_length = client->device->config_rom_length * 4;
up_read(&fw_device_rwsem);
if (ret != 0)
return -EFAULT;
Stefan Richter
committed
mutex_lock(&client->device->client_list_mutex);
client->bus_reset_closure = a->bus_reset_closure;
if (a->bus_reset != 0) {
fill_bus_reset_event(&bus_reset, client);
Stefan Richter
committed
ret = copy_to_user(u64_to_uptr(a->bus_reset),
&bus_reset, sizeof(bus_reset));
Stefan Richter
committed
if (ret == 0 && list_empty(&client->link))
list_add_tail(&client->link, &client->device->client_list);
Stefan Richter
committed
mutex_unlock(&client->device->client_list_mutex);
return ret ? -EFAULT : 0;
static int add_client_resource(struct client *client,
struct client_resource *resource, gfp_t gfp_mask)
{
unsigned long flags;
int ret;
retry:
if (idr_pre_get(&client->resource_idr, gfp_mask) == 0)
return -ENOMEM;
spin_lock_irqsave(&client->lock, flags);
if (client->in_shutdown)
ret = -ECANCELED;
else
ret = idr_get_new(&client->resource_idr, resource,
&resource->handle);
Jay Fenlason, Stefan Richter
committed
if (ret >= 0) {
schedule_if_iso_resource(resource);
Jay Fenlason, Stefan Richter
committed
}
spin_unlock_irqrestore(&client->lock, flags);
if (ret == -EAGAIN)
goto retry;
return ret < 0 ? ret : 0;
}
static int release_client_resource(struct client *client, u32 handle,
client_resource_release_fn_t release,
struct client_resource **return_resource)
struct client_resource *resource;
spin_lock_irq(&client->lock);
if (client->in_shutdown)
resource = idr_find(&client->resource_idr, handle);
if (resource && resource->release == release)
idr_remove(&client->resource_idr, handle);
spin_unlock_irq(&client->lock);
if (!(resource && resource->release == release))
return -EINVAL;
if (return_resource)
*return_resource = resource;
else
resource->release(client, resource);
client_put(client);
return 0;
}
static void release_transaction(struct client *client,
struct client_resource *resource)
{
}
static void complete_transaction(struct fw_card *card, int rcode,
void *payload, size_t length, void *data)
struct outbound_transaction_event *e = data;
struct fw_cdev_event_response *rsp = &e->response;
struct client *client = e->client;
unsigned long flags;
if (length < rsp->length)
rsp->length = length;
if (rcode == RCODE_COMPLETE)
memcpy(rsp->data, payload, rsp->length);
spin_lock_irqsave(&client->lock, flags);
idr_remove(&client->resource_idr, e->r.resource.handle);
if (client->in_shutdown)
wake_up(&client->tx_flush_wait);
spin_unlock_irqrestore(&client->lock, flags);
rsp->type = FW_CDEV_EVENT_RESPONSE;
rsp->rcode = rcode;
* In the case that sizeof(*rsp) doesn't align with the position of the
* 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.
*/
if (rsp->length <= sizeof(*rsp) - offsetof(typeof(*rsp), data))
queue_event(client, &e->event, rsp, sizeof(*rsp),
rsp->data, rsp->length);
queue_event(client, &e->event, rsp, sizeof(*rsp) + rsp->length,
NULL, 0);
/* Drop the idr's reference */
client_put(client);
static int init_request(struct client *client,
struct fw_cdev_send_request *request,
int destination_id, int speed)
struct outbound_transaction_event *e;
if (request->tcode != TCODE_STREAM_DATA &&
(request->length > 4096 || request->length > 512 << speed))
Clemens Ladisch
committed
if (request->tcode == TCODE_WRITE_QUADLET_REQUEST &&
request->length < 4)
return -EINVAL;
e = kmalloc(sizeof(*e) + request->length, GFP_KERNEL);
if (e == NULL)
return -ENOMEM;
e->client = client;
e->response.length = request->length;
e->response.closure = request->closure;
if (request->data &&
copy_from_user(e->response.data,
u64_to_uptr(request->data), request->length)) {
goto failed;
e->r.resource.release = release_transaction;
ret = add_client_resource(client, &e->r.resource, GFP_KERNEL);
if (ret < 0)
goto failed;
fw_send_request(client->device->card, &e->r.transaction,
request->tcode, destination_id, request->generation,
speed, request->offset, e->response.data,
request->length, complete_transaction, e);
return 0;
failed:
kfree(e);
static int ioctl_send_request(struct client *client, union ioctl_arg *arg)
switch (arg->send_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;
}
return init_request(client, &arg->send_request, client->device->node_id,
client->device->max_speed);
}
static inline bool is_fcp_request(struct fw_request *request)
{
return request == NULL;
}
static void release_request(struct client *client,
struct client_resource *resource)
struct inbound_transaction_resource *r = container_of(resource,
struct inbound_transaction_resource, resource);
if (is_fcp_request(r->request))
kfree(r->data);
else
fw_send_response(r->card, r->request, RCODE_CONFLICT_ERROR);
fw_card_put(r->card);
kfree(r);
}
static void handle_request(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, unsigned long long offset,
void *payload, size_t length, void *callback_data)
struct address_handler_resource *handler = callback_data;
struct inbound_transaction_resource *r;
struct inbound_transaction_event *e;
Stefan Richter
committed
size_t event_size0;
void *fcp_frame = NULL;
int ret;
/* card may be different from handler->client->device->card */
fw_card_get(card);
r = kmalloc(sizeof(*r), GFP_ATOMIC);
e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (r == NULL || e == NULL) {
fw_notify("Out of memory when allocating event\n");
goto failed;
r->request = request;
r->data = payload;
r->length = length;
if (is_fcp_request(request)) {
/*
* FIXME: Let core-transaction.c manage a
* single reference-counted copy?
*/
fcp_frame = kmemdup(payload, length, GFP_ATOMIC);
if (fcp_frame == NULL)
goto failed;
r->data = fcp_frame;
}
r->resource.release = release_request;
ret = add_client_resource(handler->client, &r->resource, GFP_ATOMIC);
if (ret < 0)
goto failed;
Stefan Richter
committed
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
if (handler->client->version < FW_CDEV_VERSION_EVENT_REQUEST2) {
struct fw_cdev_event_request *req = &e->req.request;
if (tcode & 0x10)
tcode = TCODE_LOCK_REQUEST;
req->type = FW_CDEV_EVENT_REQUEST;
req->tcode = tcode;
req->offset = offset;
req->length = length;
req->handle = r->resource.handle;
req->closure = handler->closure;
event_size0 = sizeof(*req);
} else {
struct fw_cdev_event_request2 *req = &e->req.request2;
req->type = FW_CDEV_EVENT_REQUEST2;
req->tcode = tcode;
req->offset = offset;
req->source_node_id = source;
req->destination_node_id = destination;
req->card = card->index;
req->generation = generation;
req->length = length;
req->handle = r->resource.handle;
req->closure = handler->closure;
event_size0 = sizeof(*req);
}
queue_event(handler->client, &e->event,
Stefan Richter
committed
&e->req, event_size0, r->data, length);
return;
failed:
kfree(r);
kfree(e);
kfree(fcp_frame);
if (!is_fcp_request(request))
fw_send_response(card, request, RCODE_CONFLICT_ERROR);
fw_card_put(card);
static void release_address_handler(struct client *client,
struct client_resource *resource)
struct address_handler_resource *r =
container_of(resource, struct address_handler_resource, resource);
fw_core_remove_address_handler(&r->handler);
kfree(r);
}
static int ioctl_allocate(struct client *client, union ioctl_arg *arg)
struct fw_cdev_allocate *a = &arg->allocate;
struct address_handler_resource *r;
struct fw_address_region region;
int ret;
r = kmalloc(sizeof(*r), GFP_KERNEL);
if (r == NULL)
return -ENOMEM;
if (client->version < FW_CDEV_VERSION_ALLOCATE_REGION_END)
region.end = a->offset + a->length;
else
region.end = a->region_end;
r->handler.length = a->length;
r->handler.address_callback = handle_request;
r->handler.callback_data = r;
r->closure = a->closure;
r->client = client;
ret = fw_core_add_address_handler(&r->handler, ®ion);
kfree(r);
a->offset = r->handler.offset;
r->resource.release = release_address_handler;
ret = add_client_resource(client, &r->resource, GFP_KERNEL);
if (ret < 0) {
release_address_handler(client, &r->resource);
return ret;
}
a->handle = r->resource.handle;
return 0;
}
static int ioctl_deallocate(struct client *client, union ioctl_arg *arg)
return release_client_resource(client, arg->deallocate.handle,
release_address_handler, NULL);
static int ioctl_send_response(struct client *client, union ioctl_arg *arg)
struct fw_cdev_send_response *a = &arg->send_response;
struct client_resource *resource;
struct inbound_transaction_resource *r;
if (release_client_resource(client, a->handle,
release_request, &resource) < 0)
r = container_of(resource, struct inbound_transaction_resource,
resource);
if (is_fcp_request(r->request))
goto out;
if (a->length != fw_get_response_length(r->request)) {
ret = -EINVAL;
kfree(r->request);
goto out;
}
if (copy_from_user(r->data, u64_to_uptr(a->data), a->length)) {
ret = -EFAULT;
kfree(r->request);
goto out;
fw_send_response(r->card, r->request, a->rcode);
fw_card_put(r->card);
static int ioctl_initiate_bus_reset(struct client *client, union ioctl_arg *arg)
fw_schedule_bus_reset(client->device->card, true,
arg->initiate_bus_reset.type == FW_CDEV_SHORT_RESET);
return 0;
static void release_descriptor(struct client *client,
struct client_resource *resource)
{
struct descriptor_resource *r =
container_of(resource, struct descriptor_resource, resource);
fw_core_remove_descriptor(&r->descriptor);
kfree(r);
}
static int ioctl_add_descriptor(struct client *client, union ioctl_arg *arg)
struct fw_cdev_add_descriptor *a = &arg->add_descriptor;
struct descriptor_resource *r;
int ret;
/* Access policy: Allow this ioctl only on local nodes' device files. */
if (!client->device->is_local)
return -EINVAL;
r = kmalloc(sizeof(*r) + a->length * 4, GFP_KERNEL);
if (r == NULL)
return -ENOMEM;
if (copy_from_user(r->data, u64_to_uptr(a->data), a->length * 4)) {
ret = -EFAULT;
goto failed;
}
r->descriptor.length = a->length;
r->descriptor.immediate = a->immediate;
r->descriptor.key = a->key;
r->descriptor.data = r->data;
ret = fw_core_add_descriptor(&r->descriptor);
if (ret < 0)
goto failed;
r->resource.release = release_descriptor;
ret = add_client_resource(client, &r->resource, GFP_KERNEL);
if (ret < 0) {
fw_core_remove_descriptor(&r->descriptor);
goto failed;
}
a->handle = r->resource.handle;
return 0;
failed:
kfree(r);
return ret;
}
static int ioctl_remove_descriptor(struct client *client, union ioctl_arg *arg)
return release_client_resource(client, arg->remove_descriptor.handle,
release_descriptor, NULL);
}
static void iso_callback(struct fw_iso_context *context, u32 cycle,
size_t header_length, void *header, void *data)
{
struct client *client = data;
struct iso_interrupt_event *e;
e = kmalloc(sizeof(*e) + header_length, GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
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);
static void iso_mc_callback(struct fw_iso_context *context,
dma_addr_t completed, void *data)
{
struct client *client = data;
struct iso_interrupt_mc_event *e;
e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
return;
}
e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL;
e->interrupt.closure = client->iso_closure;
e->interrupt.completed = fw_iso_buffer_lookup(&client->buffer,
completed);
queue_event(client, &e->event, &e->interrupt,
sizeof(e->interrupt), NULL, 0);
}
static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
struct fw_cdev_create_iso_context *a = &arg->create_iso_context;
struct fw_iso_context *context;
Stefan Richter
committed
BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT ||
FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE ||
FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL !=
FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL);
case FW_ISO_CONTEXT_TRANSMIT:
if (a->speed > SCODE_3200 || a->channel > 63)
return -EINVAL;
cb = iso_callback;
break;
case FW_ISO_CONTEXT_RECEIVE:
if (a->header_size < 4 || (a->header_size & 3) ||
a->channel > 63)
return -EINVAL;
cb = iso_callback;
break;
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
cb = (fw_iso_callback_t)iso_mc_callback;
break;
default:
return -EINVAL;
}
context = fw_iso_context_create(client->device->card, a->type,
a->channel, a->speed, a->header_size, cb, client);
if (IS_ERR(context))
return PTR_ERR(context);
/* We only support one context at this time. */
spin_lock_irq(&client->lock);
if (client->iso_context != NULL) {
spin_unlock_irq(&client->lock);
fw_iso_context_destroy(context);
return -EBUSY;
}
client->iso_closure = a->closure;
client->iso_context = context;
spin_unlock_irq(&client->lock);
Kristian Høgsberg
committed
return 0;
}
static int ioctl_set_iso_channels(struct client *client, union ioctl_arg *arg)