Newer
Older
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;
}
Jay Fenlason, Stefan Richter
committed
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
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;
}
free = todo == ISO_RES_DEALLOC ||
todo == ISO_RES_ALLOC_ONCE ||
todo == ISO_RES_DEALLOC_ONCE;
Jay Fenlason, Stefan Richter
committed
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,
todo == ISO_RES_ALLOC ||
todo == ISO_RES_REALLOC ||
todo == ISO_RES_ALLOC_ONCE);
Jay Fenlason, Stefan Richter
committed
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
/*
* 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)
r->channels = 1ULL << channel;
Jay Fenlason, Stefan Richter
committed
if (todo == ISO_RES_REALLOC && success)
goto out;
if (todo == ISO_RES_ALLOC || todo == ISO_RES_ALLOC_ONCE) {
Jay Fenlason, Stefan Richter
committed
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
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);
}
static void schedule_iso_resource(struct iso_resource *r)
Jay Fenlason, Stefan Richter
committed
{
client_get(r->client);
if (!schedule_delayed_work(&r->work, 0))
client_put(r->client);
Jay Fenlason, Stefan Richter
committed
}
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);
}
static int init_iso_resource(struct client *client,
struct fw_cdev_allocate_iso_resource *request, int todo)
Jay Fenlason, Stefan Richter
committed
{
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;
r->todo = todo;
Jay Fenlason, Stefan Richter
committed
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;
if (todo == ISO_RES_ALLOC) {
r->resource.release = release_iso_resource;
ret = add_client_resource(client, &r->resource, GFP_KERNEL);
if (ret < 0)
goto fail;
} else {
r->resource.release = NULL;
r->resource.handle = -1;
schedule_iso_resource(r);
Jay Fenlason, Stefan Richter
committed
request->handle = r->resource.handle;
return 0;
fail:
kfree(r);
kfree(e1);
kfree(e2);
return ret;
}
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);
}
Jay Fenlason, Stefan Richter
committed
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);
}
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);
}
Stefan Richter
committed
/*
* 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.
*/
static int ioctl_get_speed(struct client *client, void *buffer)
{
Stefan Richter
committed
return client->device->max_speed;
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;
}
/* Security policy: Only allow accesses to Units Space. */
if (request->offset < CSR_REGISTER_BASE + CSR_CONFIG_ROM_END)
return -EACCES;
return init_request(client, request, LOCAL_BUS | 0x3f, SCODE_100);
}
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
struct stream_packet {
struct fw_packet packet;
u8 data[0];
};
static void send_stream_packet_done(struct fw_packet *packet,
struct fw_card *card, int status)
{
kfree(container_of(packet, struct stream_packet, packet));
}
static int ioctl_send_stream_packet(struct client *client, void *buffer)
{
struct fw_cdev_send_stream_packet *request = buffer;
struct stream_packet *p;
p = kmalloc(sizeof(*p) + request->size, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
if (request->data &&
copy_from_user(p->data, u64_to_uptr(request->data), request->size)) {
kfree(p);
return -EFAULT;
}
fw_send_stream_packet(client->device->card, &p->packet,
request->generation, request->speed,
request->channel, request->sy, request->tag,
p->data, request->size, send_stream_packet_done);
return 0;
}
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,
ioctl_get_cycle_timer,
Jay Fenlason, Stefan Richter
committed
ioctl_allocate_iso_resource,
ioctl_deallocate_iso_resource,
ioctl_allocate_iso_resource_once,
ioctl_deallocate_iso_resource_once,
ioctl_get_speed,
ioctl_send_broadcast_request,
ioctl_send_stream_packet,
};
static int dispatch_ioctl(struct client *client,
unsigned int cmd, void __user *arg)
char buffer[256];
if (_IOC_TYPE(cmd) != '#' ||
_IOC_NR(cmd) >= ARRAY_SIZE(ioctl_handlers))
return -EINVAL;
if (_IOC_DIR(cmd) & _IOC_WRITE) {
if (_IOC_SIZE(cmd) > sizeof(buffer) ||
copy_from_user(buffer, arg, _IOC_SIZE(cmd)))
return -EFAULT;
}
ret = ioctl_handlers[_IOC_NR(cmd)](client, buffer);
if (ret < 0)
return ret;
if (_IOC_DIR(cmd) & _IOC_READ) {
if (_IOC_SIZE(cmd) > sizeof(buffer) ||
copy_to_user(arg, buffer, _IOC_SIZE(cmd)))
return -EFAULT;
static long fw_device_op_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct client *client = file->private_data;
if (fw_device_is_shutdown(client->device))
return -ENODEV;
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;
if (fw_device_is_shutdown(client->device))
return -ENODEV;
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;
Kristian Høgsberg
committed
enum dma_data_direction direction;
unsigned long size;
Kristian Høgsberg
committed
if (fw_device_is_shutdown(client->device))
return -ENODEV;
Kristian Høgsberg
committed
/* 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;
Kristian Høgsberg
committed
if (vma->vm_start & ~PAGE_MASK)
return -EINVAL;
client->vm_start = vma->vm_start;
Kristian Høgsberg
committed
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;
ret = fw_iso_buffer_init(&client->buffer, client->device->card,
page_count, direction);
if (ret < 0)
return ret;
ret = fw_iso_buffer_map(&client->buffer, vma);
if (ret < 0)
Kristian Høgsberg
committed
fw_iso_buffer_destroy(&client->buffer, client->device->card);
static int shutdown_resource(int id, void *p, void *data)
{
struct client_resource *r = p;
struct client *client = data;
r->release(client, r);
return 0;
}
static int fw_device_op_release(struct inode *inode, struct file *file)
{
struct client *client = file->private_data;
Kristian Høgsberg
committed
struct event *e, *next_e;
mutex_lock(&client->device->client_list_mutex);
list_del(&client->link);
mutex_unlock(&client->device->client_list_mutex);
if (client->iso_context)
fw_iso_context_destroy(client->iso_context);
if (client->buffer.pages)
fw_iso_buffer_destroy(&client->buffer, client->device->card);
/* Freeze client->resource_idr and client->event_list */
spin_lock_irq(&client->lock);
client->in_shutdown = true;
spin_unlock_irq(&client->lock);
idr_for_each(&client->resource_idr, shutdown_resource, client);
idr_remove_all(&client->resource_idr);
idr_destroy(&client->resource_idr);
Kristian Høgsberg
committed
list_for_each_entry_safe(e, next_e, &client->event_list, link)
kfree(e);
return 0;
}
static unsigned int fw_device_op_poll(struct file *file, poll_table * pt)
{
struct client *client = file->private_data;
Kristian Høgsberg
committed
unsigned int mask = 0;
poll_wait(file, &client->wait, pt);
Kristian Høgsberg
committed
if (fw_device_is_shutdown(client->device))
mask |= POLLHUP | POLLERR;
if (!list_empty(&client->event_list))
Kristian Høgsberg
committed
mask |= POLLIN | POLLRDNORM;
return mask;
const struct file_operations fw_device_ops = {
.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
.compat_ioctl = fw_device_op_compat_ioctl,