Newer
Older
if (a->data == 0 || client->buffer.pages == NULL ||
payload >= buffer_end) {
Kristian Høgsberg
committed
payload = 0;
buffer_end = 0;
p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets);
if (!access_ok(VERIFY_READ, p, a->size))
return -EFAULT;
end = (void __user *)p + a->size;
count = 0;
while (p < end) {
if (get_user(control, &p->control))
return -EFAULT;
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);
Kristian Høgsberg
committed
if (ctx->type == FW_ISO_CONTEXT_TRANSMIT) {
if (u.packet.header_length % 4 != 0)
return -EINVAL;
header_length = u.packet.header_length;
} else {
/*
* We require that header_length is a multiple of
* the fixed header size, ctx->header_size.
*/
Kristian Høgsberg
committed
if (ctx->header_size == 0) {
if (u.packet.header_length > 0)
return -EINVAL;
} else if (u.packet.header_length == 0 ||
u.packet.header_length % ctx->header_size != 0) {
return -EINVAL;
Kristian Høgsberg
committed
}
header_length = 0;
}
next = (struct fw_cdev_iso_packet __user *)
&p->header[header_length / 4];
if (next > end)
return -EINVAL;
if (__copy_from_user
(u.packet.header, p->header, header_length))
return -EFAULT;
if (u.packet.skip && ctx->type == FW_ISO_CONTEXT_TRANSMIT &&
u.packet.header_length + u.packet.payload_length > 0)
return -EINVAL;
if (payload + u.packet.payload_length > buffer_end)
return -EINVAL;
Kristian Høgsberg
committed
if (fw_iso_context_queue(ctx, &u.packet,
&client->buffer, payload))
break;
p = next;
payload += u.packet.payload_length;
count++;
}
a->size -= uptr_to_u64(p) - a->packets;
a->packets = uptr_to_u64(p);
a->data = client->vm_start + payload;
return count;
}
static int ioctl_start_iso(struct client *client, union ioctl_arg *arg)
struct fw_cdev_start_iso *a = &arg->start_iso;
Stefan Richter
committed
BUILD_BUG_ON(
FW_CDEV_ISO_CONTEXT_MATCH_TAG0 != FW_ISO_CONTEXT_MATCH_TAG0 ||
FW_CDEV_ISO_CONTEXT_MATCH_TAG1 != FW_ISO_CONTEXT_MATCH_TAG1 ||
FW_CDEV_ISO_CONTEXT_MATCH_TAG2 != FW_ISO_CONTEXT_MATCH_TAG2 ||
FW_CDEV_ISO_CONTEXT_MATCH_TAG3 != FW_ISO_CONTEXT_MATCH_TAG3 ||
FW_CDEV_ISO_CONTEXT_MATCH_ALL_TAGS != FW_ISO_CONTEXT_MATCH_ALL_TAGS);
if (client->iso_context == NULL || a->handle != 0)
Kristian Høgsberg
committed
return -EINVAL;
if (client->iso_context->type == FW_ISO_CONTEXT_RECEIVE &&
(a->tags == 0 || a->tags > 15 || a->sync > 15))
return -EINVAL;
return fw_iso_context_start(client->iso_context,
a->cycle, a->sync, a->tags);
static int ioctl_stop_iso(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_stop_iso *a = &arg->stop_iso;
Kristian Høgsberg
committed
if (client->iso_context == NULL || a->handle != 0)
Kristian Høgsberg
committed
return -EINVAL;
return fw_iso_context_stop(client->iso_context);
}
static int ioctl_get_cycle_timer2(struct client *client, union ioctl_arg *arg)
struct fw_cdev_get_cycle_timer2 *a = &arg->get_cycle_timer2;
struct fw_card *card = client->device->card;
struct timespec ts = {0, 0};
local_irq_disable();
cycle_time = card->driver->read_csr(card, CSR_CYCLE_TIME);
case CLOCK_REALTIME: getnstimeofday(&ts); break;
case CLOCK_MONOTONIC: do_posix_clock_monotonic_gettime(&ts); break;
case CLOCK_MONOTONIC_RAW: getrawmonotonic(&ts); break;
default:
ret = -EINVAL;
}
local_irq_enable();
a->tv_sec = ts.tv_sec;
a->tv_nsec = ts.tv_nsec;
a->cycle_timer = cycle_time;
return ret;
}
static int ioctl_get_cycle_timer(struct client *client, union ioctl_arg *arg)
struct fw_cdev_get_cycle_timer *a = &arg->get_cycle_timer;
struct fw_cdev_get_cycle_timer2 ct2;
ct2.clk_id = CLOCK_REALTIME;
ioctl_get_cycle_timer2(client, (union ioctl_arg *)&ct2);
a->local_time = ct2.tv_sec * USEC_PER_SEC + ct2.tv_nsec / NSEC_PER_USEC;
a->cycle_timer = ct2.cycle_timer;
return 0;
}
Jay Fenlason, Stefan Richter
committed
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)) {
schedule_iso_resource(r, DIV_ROUND_UP(HZ, 3));
Jay Fenlason, Stefan Richter
committed
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,
r->transaction_data);
Jay Fenlason, Stefan Richter
committed
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
/*
* 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
e = r->e_alloc;
r->e_alloc = NULL;
} else {
e = r->e_dealloc;
r->e_dealloc = NULL;
}
e->iso_resource.handle = r->resource.handle;
e->iso_resource.channel = channel;
e->iso_resource.bandwidth = bandwidth;
Jay Fenlason, Stefan Richter
committed
queue_event(client, &e->event,
&e->iso_resource, sizeof(e->iso_resource), NULL, 0);
Jay Fenlason, Stefan Richter
committed
if (free) {
cancel_delayed_work(&r->work);
kfree(r->e_alloc);
kfree(r->e_dealloc);
kfree(r);
}
out:
client_put(client);
}
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, 0);
Jay Fenlason, Stefan Richter
committed
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->iso_resource.closure = request->closure;
e1->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED;
e2->iso_resource.closure = request->closure;
e2->iso_resource.type = FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED;
Jay Fenlason, Stefan Richter
committed
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, 0);
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,
union ioctl_arg *arg)
return init_iso_resource(client,
&arg->allocate_iso_resource, ISO_RES_ALLOC);
static int ioctl_deallocate_iso_resource(struct client *client,
union ioctl_arg *arg)
Jay Fenlason, Stefan Richter
committed
{
return release_client_resource(client,
arg->deallocate.handle, release_iso_resource, NULL);
Jay Fenlason, Stefan Richter
committed
}
static int ioctl_allocate_iso_resource_once(struct client *client,
union ioctl_arg *arg)
return init_iso_resource(client,
&arg->allocate_iso_resource, ISO_RES_ALLOC_ONCE);
static int ioctl_deallocate_iso_resource_once(struct client *client,
union ioctl_arg *arg)
return init_iso_resource(client,
&arg->allocate_iso_resource, 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, union ioctl_arg *arg)
Stefan Richter
committed
return client->device->max_speed;
static int ioctl_send_broadcast_request(struct client *client,
union ioctl_arg *arg)
{
struct fw_cdev_send_request *a = &arg->send_request;
case TCODE_WRITE_QUADLET_REQUEST:
case TCODE_WRITE_BLOCK_REQUEST:
break;
default:
return -EINVAL;
}
/* Security policy: Only allow accesses to Units Space. */
if (a->offset < CSR_REGISTER_BASE + CSR_CONFIG_ROM_END)
return -EACCES;
return init_request(client, a, LOCAL_BUS | 0x3f, SCODE_100);
}
static int ioctl_send_stream_packet(struct client *client, union ioctl_arg *arg)
struct fw_cdev_send_stream_packet *a = &arg->send_stream_packet;
struct fw_cdev_send_request request;
int dest;
if (a->speed > client->device->card->link_speed ||
a->length > 1024 << a->speed)
if (a->tag > 3 || a->channel > 63 || a->sy > 15)
return -EINVAL;
dest = fw_stream_packet_destination_id(a->tag, a->channel, a->sy);
request.tcode = TCODE_STREAM_DATA;
request.length = a->length;
request.closure = a->closure;
request.data = a->data;
request.generation = a->generation;
return init_request(client, &request, dest, a->speed);
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
static void outbound_phy_packet_callback(struct fw_packet *packet,
struct fw_card *card, int status)
{
struct outbound_phy_packet_event *e =
container_of(packet, struct outbound_phy_packet_event, p);
switch (status) {
/* expected: */
case ACK_COMPLETE: e->phy_packet.rcode = RCODE_COMPLETE; break;
/* should never happen with PHY packets: */
case ACK_PENDING: e->phy_packet.rcode = RCODE_COMPLETE; break;
case ACK_BUSY_X:
case ACK_BUSY_A:
case ACK_BUSY_B: e->phy_packet.rcode = RCODE_BUSY; break;
case ACK_DATA_ERROR: e->phy_packet.rcode = RCODE_DATA_ERROR; break;
case ACK_TYPE_ERROR: e->phy_packet.rcode = RCODE_TYPE_ERROR; break;
/* stale generation; cancelled; on certain controllers: no ack */
default: e->phy_packet.rcode = status; break;
}
queue_event(e->client, &e->event,
&e->phy_packet, sizeof(e->phy_packet), NULL, 0);
client_put(e->client);
}
static int ioctl_send_phy_packet(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_send_phy_packet *a = &arg->send_phy_packet;
struct fw_card *card = client->device->card;
struct outbound_phy_packet_event *e;
/* Access policy: Allow this ioctl only on local nodes' device files. */
if (!client->device->is_local)
return -ENOSYS;
e = kzalloc(sizeof(*e), GFP_KERNEL);
if (e == NULL)
return -ENOMEM;
client_get(client);
e->client = client;
e->p.speed = SCODE_100;
e->p.generation = a->generation;
e->p.header[0] = a->data[0];
e->p.header[1] = a->data[1];
e->p.header_length = 8;
e->p.callback = outbound_phy_packet_callback;
e->phy_packet.closure = a->closure;
e->phy_packet.type = FW_CDEV_EVENT_PHY_PACKET_SENT;
card->driver->send_request(card, &e->p);
return 0;
}
static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
[0x00] = ioctl_get_info,
[0x01] = ioctl_send_request,
[0x02] = ioctl_allocate,
[0x03] = ioctl_deallocate,
[0x04] = ioctl_send_response,
[0x05] = ioctl_initiate_bus_reset,
[0x06] = ioctl_add_descriptor,
[0x07] = ioctl_remove_descriptor,
[0x08] = ioctl_create_iso_context,
[0x09] = ioctl_queue_iso,
[0x0a] = ioctl_start_iso,
[0x0b] = ioctl_stop_iso,
[0x0c] = ioctl_get_cycle_timer,
[0x0d] = ioctl_allocate_iso_resource,
[0x0e] = ioctl_deallocate_iso_resource,
[0x0f] = ioctl_allocate_iso_resource_once,
[0x10] = ioctl_deallocate_iso_resource_once,
[0x11] = ioctl_get_speed,
[0x12] = ioctl_send_broadcast_request,
[0x13] = ioctl_send_stream_packet,
[0x14] = ioctl_get_cycle_timer2,
[0x15] = ioctl_send_phy_packet,
};
static int dispatch_ioctl(struct client *client,
unsigned int cmd, void __user *arg)
if (fw_device_is_shutdown(client->device))
return -ENODEV;
if (_IOC_TYPE(cmd) != '#' ||
_IOC_NR(cmd) >= ARRAY_SIZE(ioctl_handlers) ||
_IOC_SIZE(cmd) > sizeof(buffer))
return -EINVAL;
if (_IOC_DIR(cmd) == _IOC_READ)
memset(&buffer, 0, _IOC_SIZE(cmd));
if (_IOC_DIR(cmd) & _IOC_WRITE)
if (copy_from_user(&buffer, arg, _IOC_SIZE(cmd)))
return -EFAULT;
ret = ioctl_handlers[_IOC_NR(cmd)](client, &buffer);
if (_IOC_DIR(cmd) & _IOC_READ)
if (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)
return dispatch_ioctl(file->private_data, cmd, (void __user *)arg);
}
#ifdef CONFIG_COMPAT
static long fw_device_op_compat_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
return dispatch_ioctl(file->private_data, 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 *resource = p;
struct client *client = data;
resource->release(client, resource);
return 0;
}
static int fw_device_op_release(struct inode *inode, struct file *file)
{
struct client *client = file->private_data;
struct event *event, *next_event;
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);
list_for_each_entry_safe(event, next_event, &client->event_list, link)
kfree(event);
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,
.llseek = no_llseek,
.open = fw_device_op_open,
.read = fw_device_op_read,
.unlocked_ioctl = fw_device_op_ioctl,
.mmap = fw_device_op_mmap,
.release = fw_device_op_release,
.poll = fw_device_op_poll,
#ifdef CONFIG_COMPAT
.compat_ioctl = fw_device_op_compat_ioctl,