cciss: Fix race between disk-adding code and interrupt handler
[safe/jmp/linux-2.6] / drivers / firewire / fw-cdev.c
index 88b8fd9..4a54192 100644 (file)
@@ -1,8 +1,7 @@
-/*                                             -*- 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;
@@ -46,6 +45,11 @@ struct client_resource {
        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;
@@ -105,15 +109,17 @@ static int fw_device_op_open(struct inode *inode, struct file *file)
        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);
@@ -139,11 +145,10 @@ static void queue_event(struct client *client, struct event *event,
        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
@@ -203,12 +208,13 @@ fill_bus_reset_event(struct fw_cdev_event_bus_reset *event,
 
        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
@@ -232,7 +238,7 @@ queue_bus_reset_event(struct client *client)
 {
        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;
@@ -241,7 +247,7 @@ queue_bus_reset_event(struct client *client)
        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)
@@ -263,27 +269,34 @@ static int ioctl_get_info(struct client *client, void *buffer)
 {
        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;
        }
 
@@ -360,11 +373,11 @@ complete_transaction(struct fw_card *card, int rcode,
        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;
@@ -374,7 +387,7 @@ static ssize_t ioctl_send_request(struct client *client, void *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;
 
@@ -396,15 +409,15 @@ static ssize_t ioctl_send_request(struct client *client, void *buffer)
                        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 {
@@ -449,8 +462,8 @@ handle_request(struct fw_card *card, struct fw_request *r,
        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);
@@ -473,7 +486,7 @@ handle_request(struct fw_card *card, struct fw_request *r,
        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
@@ -493,7 +506,7 @@ static int ioctl_allocate(struct client *client, void *buffer)
        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;
 
@@ -580,7 +593,7 @@ static int ioctl_add_descriptor(struct client *client, void *buffer)
                return -EINVAL;
 
        descriptor =
-               kmalloc(sizeof *descriptor + request->length * 4, GFP_KERNEL);
+               kmalloc(sizeof(*descriptor) + request->length * 4, GFP_KERNEL);
        if (descriptor == NULL)
                return -ENOMEM;
 
@@ -620,25 +633,29 @@ iso_callback(struct fw_iso_context *context, u32 cycle,
             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;
@@ -660,15 +677,17 @@ static int ioctl_create_iso_context(struct client *client, void *buffer)
                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;
@@ -676,12 +695,21 @@ static int ioctl_create_iso_context(struct client *client, void *buffer)
        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;
@@ -691,13 +719,15 @@ static int ioctl_queue_iso(struct client *client, void *buffer)
        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;
@@ -707,21 +737,30 @@ static int ioctl_queue_iso(struct client *client, void *buffer)
                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;
@@ -764,8 +803,9 @@ static int ioctl_start_iso(struct client *client, void *buffer)
 {
        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;
@@ -782,12 +822,34 @@ static int ioctl_stop_iso(struct client *client, void *buffer)
 {
        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,
@@ -801,6 +863,7 @@ static int (* const ioctl_handlers[])(struct client *client, void *buffer) = {
        ioctl_queue_iso,
        ioctl_start_iso,
        ioctl_stop_iso,
+       ioctl_get_cycle_timer,
 };
 
 static int
@@ -814,7 +877,7 @@ dispatch_ioctl(struct client *client, unsigned int cmd, void __user *arg)
                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;
        }
@@ -824,7 +887,7 @@ dispatch_ioctl(struct client *client, unsigned int cmd, void __user *arg)
                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;
        }
@@ -908,8 +971,10 @@ static int fw_device_op_release(struct inode *inode, struct file *file)
        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);