-/* -*- c-basic-offset: 8 -*-
+/*
+ * Char device for device raw access
*
- * fw-device-cdev.c - Char device for device raw access
- *
- * Copyright (C) 2005-2006 Kristian Hoegsberg <krh@bitplanet.net>
+ * 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
#include <linux/device.h>
#include <linux/vmalloc.h>
#include <linux/poll.h>
+#include <linux/preempt.h>
+#include <linux/time.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/idr.h>
#include <linux/compat.h>
#include <linux/firewire-cdev.h>
+#include <asm/system.h>
#include <asm/uaccess.h>
#include "fw-transaction.h"
#include "fw-topology.h"
#include "fw-device.h"
-/* dequeue_event() just kfree()'s the event, so the event has to be
- * the first field in the struct. */
-
struct client;
struct client_resource {
struct list_head link;
u32 handle;
};
+/*
+ * dequeue_event() just kfree()'s the event, so the event has to be
+ * the first field in the struct.
+ */
+
struct event {
struct { void *data; size_t size; } v[2];
struct list_head link;
struct client *client;
unsigned long flags;
- device = fw_device_from_devt(inode->i_rdev);
+ device = fw_device_get_by_devt(inode->i_rdev);
if (device == NULL)
return -ENODEV;
- client = kzalloc(sizeof *client, GFP_KERNEL);
- if (client == NULL)
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (client == NULL) {
+ fw_device_put(device);
return -ENOMEM;
+ }
- client->device = fw_device_get(device);
+ client->device = device;
INIT_LIST_HEAD(&client->event_list);
INIT_LIST_HEAD(&client->resource_list);
spin_lock_init(&client->lock);
event->v[1].size = size1;
spin_lock_irqsave(&client->lock, flags);
-
list_add_tail(&event->link, &client->event_list);
- wake_up_interruptible(&client->wait);
-
spin_unlock_irqrestore(&client->lock, flags);
+
+ wake_up_interruptible(&client->wait);
}
static int
event->closure = client->bus_reset_closure;
event->type = FW_CDEV_EVENT_BUS_RESET;
+ event->generation = client->device->generation;
+ smp_rmb(); /* node_id must not be older than generation */
event->node_id = client->device->node_id;
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;
}
static void
{
struct bus_reset *bus_reset;
- bus_reset = kzalloc(sizeof *bus_reset, GFP_ATOMIC);
+ bus_reset = kzalloc(sizeof(*bus_reset), GFP_ATOMIC);
if (bus_reset == NULL) {
fw_notify("Out of memory when allocating bus reset event\n");
return;
fill_bus_reset_event(&bus_reset->reset, client);
queue_event(client, &bus_reset->event,
- &bus_reset->reset, sizeof bus_reset->reset, NULL, 0);
+ &bus_reset->reset, sizeof(bus_reset->reset), NULL, 0);
}
void fw_device_cdev_update(struct fw_device *device)
{
struct fw_cdev_get_info *get_info = buffer;
struct fw_cdev_event_bus_reset bus_reset;
+ unsigned long ret = 0;
client->version = get_info->version;
get_info->version = FW_CDEV_VERSION;
+ down_read(&fw_device_rwsem);
+
if (get_info->rom != 0) {
void __user *uptr = u64_to_uptr(get_info->rom);
size_t want = get_info->rom_length;
size_t have = client->device->config_rom_length * 4;
- if (copy_to_user(uptr, client->device->config_rom,
- min(want, have)))
- return -EFAULT;
+ ret = copy_to_user(uptr, client->device->config_rom,
+ min(want, have));
}
get_info->rom_length = client->device->config_rom_length * 4;
+ up_read(&fw_device_rwsem);
+
+ if (ret != 0)
+ return -EFAULT;
+
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);
fill_bus_reset_event(&bus_reset, client);
- if (copy_to_user(uptr, &bus_reset, sizeof bus_reset))
+ if (copy_to_user(uptr, &bus_reset, sizeof(bus_reset)))
return -EFAULT;
}
response->response.type = FW_CDEV_EVENT_RESPONSE;
response->response.rcode = rcode;
queue_event(client, &response->event,
- &response->response, sizeof response->response,
+ &response->response, sizeof(response->response),
response->response.data, response->response.length);
}
-static ssize_t ioctl_send_request(struct client *client, void *buffer)
+static int ioctl_send_request(struct client *client, void *buffer)
{
struct fw_device *device = client->device;
struct fw_cdev_send_request *request = buffer;
if (request->length > 4096)
return -EINVAL;
- response = kmalloc(sizeof *response + request->length, GFP_KERNEL);
+ response = kmalloc(sizeof(*response) + request->length, GFP_KERNEL);
if (response == NULL)
return -ENOMEM;
request->tcode & 0x1f,
device->node->node_id,
request->generation,
- device->node->max_speed,
+ device->max_speed,
request->offset,
response->response.data, request->length,
complete_transaction, response);
if (request->data)
- return sizeof request + request->length;
+ return sizeof(request) + request->length;
else
- return sizeof request;
+ return sizeof(request);
}
struct address_handler {
struct request_event *e;
struct client *client = handler->client;
- request = kmalloc(sizeof *request, GFP_ATOMIC);
- e = kmalloc(sizeof *e, GFP_ATOMIC);
+ request = kmalloc(sizeof(*request), GFP_ATOMIC);
+ e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (request == NULL || e == NULL) {
kfree(request);
kfree(e);
e->request.closure = handler->closure;
queue_event(client, &e->event,
- &e->request, sizeof e->request, payload, length);
+ &e->request, sizeof(e->request), payload, length);
}
static void
struct address_handler *handler;
struct fw_address_region region;
- handler = kmalloc(sizeof *handler, GFP_KERNEL);
+ handler = kmalloc(sizeof(*handler), GFP_KERNEL);
if (handler == NULL)
return -ENOMEM;
return -EINVAL;
descriptor =
- kmalloc(sizeof *descriptor + request->length * 4, GFP_KERNEL);
+ kmalloc(sizeof(*descriptor) + request->length * 4, GFP_KERNEL);
if (descriptor == NULL)
return -ENOMEM;
size_t header_length, void *header, void *data)
{
struct client *client = data;
- struct iso_interrupt *interrupt;
+ struct iso_interrupt *irq;
- interrupt = kzalloc(sizeof *interrupt + header_length, GFP_ATOMIC);
- if (interrupt == NULL)
+ irq = kzalloc(sizeof(*irq) + header_length, GFP_ATOMIC);
+ if (irq == NULL)
return;
- interrupt->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT;
- interrupt->interrupt.closure = client->iso_closure;
- interrupt->interrupt.cycle = cycle;
- interrupt->interrupt.header_length = header_length;
- memcpy(interrupt->interrupt.header, header, header_length);
- queue_event(client, &interrupt->event,
- &interrupt->interrupt,
- sizeof interrupt->interrupt + header_length, NULL, 0);
+ 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);
}
static int ioctl_create_iso_context(struct client *client, void *buffer)
{
struct fw_cdev_create_iso_context *request = buffer;
+ struct fw_iso_context *context;
+
+ /* We only support one context at this time. */
+ if (client->iso_context != NULL)
+ return -EBUSY;
if (request->channel > 63)
return -EINVAL;
return -EINVAL;
}
+ 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);
+
client->iso_closure = request->closure;
- client->iso_context = fw_iso_context_create(client->device->card,
- request->type,
- request->channel,
- request->speed,
- request->header_size,
- iso_callback, client);
- if (IS_ERR(client->iso_context))
- return PTR_ERR(client->iso_context);
+ client->iso_context = context;
/* We only support one context at this time. */
request->handle = 0;
return 0;
}
+/* 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)
+
static int ioctl_queue_iso(struct client *client, void *buffer)
{
struct fw_cdev_queue_iso *request = buffer;
struct fw_cdev_iso_packet __user *p, *end, *next;
struct fw_iso_context *ctx = client->iso_context;
unsigned long payload, buffer_end, header_length;
+ u32 control;
int count;
struct {
struct fw_iso_packet packet;
if (ctx == NULL || request->handle != 0)
return -EINVAL;
- /* If the user passes a non-NULL data pointer, has mmap()'ed
+ /*
+ * 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
* 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 request->data pointer is ignored.*/
+ * and the request->data pointer is ignored.
+ */
payload = (unsigned long)request->data - client->vm_start;
buffer_end = client->buffer.page_count << PAGE_SHIFT;
buffer_end = 0;
}
- if (!access_ok(VERIFY_READ, request->packets, request->size))
+ p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(request->packets);
+
+ if (!access_ok(VERIFY_READ, p, request->size))
return -EFAULT;
- p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(request->packets);
end = (void __user *)p + request->size;
count = 0;
while (p < end) {
- if (__copy_from_user(&u.packet, p, sizeof *p))
+ 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);
if (ctx->type == FW_ISO_CONTEXT_TRANSMIT) {
header_length = u.packet.header_length;
} else {
- /* We require that header_length is a multiple of
- * the fixed header size, ctx->header_size */
+ /*
+ * We require that header_length is a multiple of
+ * the fixed header size, ctx->header_size.
+ */
if (ctx->header_size == 0) {
if (u.packet.header_length > 0)
return -EINVAL;
{
struct fw_cdev_start_iso *request = buffer;
- if (request->handle != 0)
+ if (client->iso_context == NULL || request->handle != 0)
return -EINVAL;
+
if (client->iso_context->type == FW_ISO_CONTEXT_RECEIVE) {
if (request->tags == 0 || request->tags > 15)
return -EINVAL;
{
struct fw_cdev_stop_iso *request = buffer;
- if (request->handle != 0)
+ if (client->iso_context == NULL || request->handle != 0)
return -EINVAL;
return fw_iso_context_stop(client->iso_context);
}
+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;
+}
+
static int (* const ioctl_handlers[])(struct client *client, void *buffer) = {
ioctl_get_info,
ioctl_send_request,
ioctl_queue_iso,
ioctl_start_iso,
ioctl_stop_iso,
+ ioctl_get_cycle_timer,
};
static int
return -EINVAL;
if (_IOC_DIR(cmd) & _IOC_WRITE) {
- if (_IOC_SIZE(cmd) > sizeof buffer ||
+ if (_IOC_SIZE(cmd) > sizeof(buffer) ||
copy_from_user(buffer, arg, _IOC_SIZE(cmd)))
return -EFAULT;
}
return retval;
if (_IOC_DIR(cmd) & _IOC_READ) {
- if (_IOC_SIZE(cmd) > sizeof buffer ||
+ if (_IOC_SIZE(cmd) > sizeof(buffer) ||
copy_to_user(arg, buffer, _IOC_SIZE(cmd)))
return -EFAULT;
}
list_for_each_entry_safe(r, next_r, &client->resource_list, link)
r->release(client, r);
- /* FIXME: We should wait for the async tasklets to stop
- * running before freeing the memory. */
+ /*
+ * FIXME: We should wait for the async tasklets to stop
+ * running before freeing the memory.
+ */
list_for_each_entry_safe(e, next_e, &client->event_list, link)
kfree(e);