Newer
Older
u8 header[256];
} u;
if (ctx == NULL || a->handle != 0)
return -EINVAL;
/*
* If the user passes a non-NULL data pointer, has mmap()'ed
* the iso buffer, and the pointer points inside the buffer,
* we setup the payload pointers accordingly. Otherwise we
Kristian Høgsberg
committed
* set them both to 0, which will still let packets with
* payload_length == 0 through. In other words, if no packets
* use the indirect payload, the iso buffer need not be mapped
* and the a->data pointer is ignored.
payload = (unsigned long)a->data - client->vm_start;
buffer_end = client->buffer.page_count << PAGE_SHIFT;
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
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
/*
* 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);
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;
}
e->phy_packet.data[0] = packet->timestamp;
queue_event(e->client, &e->event, &e->phy_packet,
sizeof(e->phy_packet) + e->phy_packet.length, 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) + 4, 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;
if (is_ping_packet(a->data))
e->phy_packet.length = 4;
card->driver->send_request(card, &e->p);
return 0;
}
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
static int ioctl_receive_phy_packets(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_receive_phy_packets *a = &arg->receive_phy_packets;
struct fw_card *card = client->device->card;
/* Access policy: Allow this ioctl only on local nodes' device files. */
if (!client->device->is_local)
return -ENOSYS;
spin_lock_irq(&card->lock);
list_move_tail(&client->phy_receiver_link, &card->phy_receiver_list);
client->phy_receiver_closure = a->closure;
spin_unlock_irq(&card->lock);
return 0;
}
void fw_cdev_handle_phy_packet(struct fw_card *card, struct fw_packet *p)
{
struct client *client;
struct inbound_phy_packet_event *e;
unsigned long flags;
spin_lock_irqsave(&card->lock, flags);
list_for_each_entry(client, &card->phy_receiver_list, phy_receiver_link) {
e = kmalloc(sizeof(*e) + 8, GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
break;
}
e->phy_packet.closure = client->phy_receiver_closure;
e->phy_packet.type = FW_CDEV_EVENT_PHY_PACKET_RECEIVED;
e->phy_packet.rcode = RCODE_COMPLETE;
e->phy_packet.length = 8;
e->phy_packet.data[0] = p->header[1];
e->phy_packet.data[1] = p->header[2];
queue_event(client, &e->event,
&e->phy_packet, sizeof(e->phy_packet) + 8, NULL, 0);
}
spin_unlock_irqrestore(&card->lock, flags);
}
static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
[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,
[0x16] = ioctl_receive_phy_packets,
};
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;
spin_lock_irq(&client->device->card->lock);
list_del(&client->phy_receiver_link);
spin_unlock_irq(&client->device->card->lock);
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,