Commit 45ee3199 authored by Jay Fenlason's avatar Jay Fenlason Committed by Stefan Richter

firewire: cdev: use an idr rather than a linked list for resources

The current code uses a linked list and a counter for storing
resources and the corresponding handle numbers.  By changing to an idr
we can be safe from counter wrap-around giving two resources the same
handle.

Furthermore, the deallocation ioctls now check whether the resource to
be freed is of the intended type.
Signed-off-by: default avatarJay Fenlason <fenlason@redhat.com>

Some rework by Stefan R:
  - The idr API documentation says we get an ID within 0...0x7fffffff.
    Hence we can rest assured that idr handles fit into cdev handles.
  - Fix some races.  Add a client->in_shutdown flag for this purpose.
  - Add allocation retry to add_client_resource().
  - It is possible to use idr_for_each() in fw_device_op_release().
  - Fix ioctl_send_response() regression.
  - Small style changes.
Signed-off-by: default avatarStefan Richter <stefanr@s5r6.in-berlin.de>
parent 97811e34
...@@ -41,10 +41,12 @@ ...@@ -41,10 +41,12 @@
#include "fw-device.h" #include "fw-device.h"
struct client; struct client;
struct client_resource;
typedef void (*client_resource_release_fn_t)(struct client *,
struct client_resource *);
struct client_resource { struct client_resource {
struct list_head link; client_resource_release_fn_t release;
void (*release)(struct client *client, struct client_resource *r); int handle;
u32 handle;
}; };
/* /*
...@@ -78,9 +80,10 @@ struct iso_interrupt { ...@@ -78,9 +80,10 @@ struct iso_interrupt {
struct client { struct client {
u32 version; u32 version;
struct fw_device *device; struct fw_device *device;
spinlock_t lock; spinlock_t lock;
u32 resource_handle; bool in_shutdown;
struct list_head resource_list; struct idr resource_idr;
struct list_head event_list; struct list_head event_list;
wait_queue_head_t wait; wait_queue_head_t wait;
u64 bus_reset_closure; u64 bus_reset_closure;
...@@ -126,9 +129,9 @@ static int fw_device_op_open(struct inode *inode, struct file *file) ...@@ -126,9 +129,9 @@ static int fw_device_op_open(struct inode *inode, struct file *file)
} }
client->device = device; client->device = device;
INIT_LIST_HEAD(&client->event_list);
INIT_LIST_HEAD(&client->resource_list);
spin_lock_init(&client->lock); spin_lock_init(&client->lock);
idr_init(&client->resource_idr);
INIT_LIST_HEAD(&client->event_list);
init_waitqueue_head(&client->wait); init_waitqueue_head(&client->wait);
file->private_data = client; file->private_data = client;
...@@ -151,6 +154,9 @@ static void queue_event(struct client *client, struct event *event, ...@@ -151,6 +154,9 @@ static void queue_event(struct client *client, struct event *event,
event->v[1].size = size1; event->v[1].size = size1;
spin_lock_irqsave(&client->lock, flags); spin_lock_irqsave(&client->lock, flags);
if (client->in_shutdown)
kfree(event);
else
list_add_tail(&event->link, &client->event_list); list_add_tail(&event->link, &client->event_list);
spin_unlock_irqrestore(&client->lock, flags); spin_unlock_irqrestore(&client->lock, flags);
...@@ -310,34 +316,49 @@ static int ioctl_get_info(struct client *client, void *buffer) ...@@ -310,34 +316,49 @@ static int ioctl_get_info(struct client *client, void *buffer)
return 0; return 0;
} }
static void static int
add_client_resource(struct client *client, struct client_resource *resource) add_client_resource(struct client *client, struct client_resource *resource,
gfp_t gfp_mask)
{ {
unsigned long flags; unsigned long flags;
int ret;
retry:
if (idr_pre_get(&client->resource_idr, gfp_mask) == 0)
return -ENOMEM;
spin_lock_irqsave(&client->lock, flags); spin_lock_irqsave(&client->lock, flags);
list_add_tail(&resource->link, &client->resource_list); if (client->in_shutdown)
resource->handle = client->resource_handle++; ret = -ECANCELED;
else
ret = idr_get_new(&client->resource_idr, resource,
&resource->handle);
spin_unlock_irqrestore(&client->lock, flags); spin_unlock_irqrestore(&client->lock, flags);
if (ret == -EAGAIN)
goto retry;
return ret < 0 ? ret : 0;
} }
static int static int
release_client_resource(struct client *client, u32 handle, release_client_resource(struct client *client, u32 handle,
client_resource_release_fn_t release,
struct client_resource **resource) struct client_resource **resource)
{ {
struct client_resource *r; struct client_resource *r;
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&client->lock, flags); spin_lock_irqsave(&client->lock, flags);
list_for_each_entry(r, &client->resource_list, link) { if (client->in_shutdown)
if (r->handle == handle) { r = NULL;
list_del(&r->link); else
break; r = idr_find(&client->resource_idr, handle);
} if (r && r->release == release)
} idr_remove(&client->resource_idr, handle);
spin_unlock_irqrestore(&client->lock, flags); spin_unlock_irqrestore(&client->lock, flags);
if (&r->link == &client->resource_list) if (!(r && r->release == release))
return -EINVAL; return -EINVAL;
if (resource) if (resource)
...@@ -372,7 +393,12 @@ complete_transaction(struct fw_card *card, int rcode, ...@@ -372,7 +393,12 @@ complete_transaction(struct fw_card *card, int rcode,
memcpy(r->data, payload, r->length); memcpy(r->data, payload, r->length);
spin_lock_irqsave(&client->lock, flags); spin_lock_irqsave(&client->lock, flags);
list_del(&response->resource.link); /*
* If called while in shutdown, the idr tree must be left untouched.
* The idr handle will be removed later.
*/
if (!client->in_shutdown)
idr_remove(&client->resource_idr, response->resource.handle);
spin_unlock_irqrestore(&client->lock, flags); spin_unlock_irqrestore(&client->lock, flags);
r->type = FW_CDEV_EVENT_RESPONSE; r->type = FW_CDEV_EVENT_RESPONSE;
...@@ -416,7 +442,7 @@ static int ioctl_send_request(struct client *client, void *buffer) ...@@ -416,7 +442,7 @@ static int ioctl_send_request(struct client *client, void *buffer)
copy_from_user(response->response.data, copy_from_user(response->response.data,
u64_to_uptr(request->data), request->length)) { u64_to_uptr(request->data), request->length)) {
ret = -EFAULT; ret = -EFAULT;
goto err; goto failed;
} }
switch (request->tcode) { switch (request->tcode) {
...@@ -434,11 +460,13 @@ static int ioctl_send_request(struct client *client, void *buffer) ...@@ -434,11 +460,13 @@ static int ioctl_send_request(struct client *client, void *buffer)
break; break;
default: default:
ret = -EINVAL; ret = -EINVAL;
goto err; goto failed;
} }
response->resource.release = release_transaction; response->resource.release = release_transaction;
add_client_resource(client, &response->resource); ret = add_client_resource(client, &response->resource, GFP_KERNEL);
if (ret < 0)
goto failed;
fw_send_request(device->card, &response->transaction, fw_send_request(device->card, &response->transaction,
request->tcode & 0x1f, request->tcode & 0x1f,
...@@ -453,7 +481,7 @@ static int ioctl_send_request(struct client *client, void *buffer) ...@@ -453,7 +481,7 @@ static int ioctl_send_request(struct client *client, void *buffer)
return sizeof(request) + request->length; return sizeof(request) + request->length;
else else
return sizeof(request); return sizeof(request);
err: failed:
kfree(response); kfree(response);
return ret; return ret;
...@@ -500,22 +528,21 @@ handle_request(struct fw_card *card, struct fw_request *r, ...@@ -500,22 +528,21 @@ handle_request(struct fw_card *card, struct fw_request *r,
struct request *request; struct request *request;
struct request_event *e; struct request_event *e;
struct client *client = handler->client; struct client *client = handler->client;
int ret;
request = kmalloc(sizeof(*request), GFP_ATOMIC); request = kmalloc(sizeof(*request), GFP_ATOMIC);
e = kmalloc(sizeof(*e), GFP_ATOMIC); e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (request == NULL || e == NULL) { if (request == NULL || e == NULL)
kfree(request); goto failed;
kfree(e);
fw_send_response(card, r, RCODE_CONFLICT_ERROR);
return;
}
request->request = r; request->request = r;
request->data = payload; request->data = payload;
request->length = length; request->length = length;
request->resource.release = release_request; request->resource.release = release_request;
add_client_resource(client, &request->resource); ret = add_client_resource(client, &request->resource, GFP_ATOMIC);
if (ret < 0)
goto failed;
e->request.type = FW_CDEV_EVENT_REQUEST; e->request.type = FW_CDEV_EVENT_REQUEST;
e->request.tcode = tcode; e->request.tcode = tcode;
...@@ -526,6 +553,12 @@ handle_request(struct fw_card *card, struct fw_request *r, ...@@ -526,6 +553,12 @@ handle_request(struct fw_card *card, struct fw_request *r,
queue_event(client, &e->event, queue_event(client, &e->event,
&e->request, sizeof(e->request), payload, length); &e->request, sizeof(e->request), payload, length);
return;
failed:
kfree(request);
kfree(e);
fw_send_response(card, r, RCODE_CONFLICT_ERROR);
} }
static void static void
...@@ -544,6 +577,7 @@ static int ioctl_allocate(struct client *client, void *buffer) ...@@ -544,6 +577,7 @@ static int ioctl_allocate(struct client *client, void *buffer)
struct fw_cdev_allocate *request = buffer; struct fw_cdev_allocate *request = buffer;
struct address_handler *handler; struct address_handler *handler;
struct fw_address_region region; struct fw_address_region region;
int ret;
handler = kmalloc(sizeof(*handler), GFP_KERNEL); handler = kmalloc(sizeof(*handler), GFP_KERNEL);
if (handler == NULL) if (handler == NULL)
...@@ -563,7 +597,11 @@ static int ioctl_allocate(struct client *client, void *buffer) ...@@ -563,7 +597,11 @@ static int ioctl_allocate(struct client *client, void *buffer)
} }
handler->resource.release = release_address_handler; handler->resource.release = release_address_handler;
add_client_resource(client, &handler->resource); ret = add_client_resource(client, &handler->resource, GFP_KERNEL);
if (ret < 0) {
release_address_handler(client, &handler->resource);
return ret;
}
request->handle = handler->resource.handle; request->handle = handler->resource.handle;
return 0; return 0;
...@@ -573,7 +611,8 @@ static int ioctl_deallocate(struct client *client, void *buffer) ...@@ -573,7 +611,8 @@ static int ioctl_deallocate(struct client *client, void *buffer)
{ {
struct fw_cdev_deallocate *request = buffer; struct fw_cdev_deallocate *request = buffer;
return release_client_resource(client, request->handle, NULL); return release_client_resource(client, request->handle,
release_address_handler, NULL);
} }
static int ioctl_send_response(struct client *client, void *buffer) static int ioctl_send_response(struct client *client, void *buffer)
...@@ -582,8 +621,10 @@ static int ioctl_send_response(struct client *client, void *buffer) ...@@ -582,8 +621,10 @@ static int ioctl_send_response(struct client *client, void *buffer)
struct client_resource *resource; struct client_resource *resource;
struct request *r; struct request *r;
if (release_client_resource(client, request->handle, &resource) < 0) if (release_client_resource(client, request->handle,
release_request, &resource) < 0)
return -EINVAL; return -EINVAL;
r = container_of(resource, struct request, resource); r = container_of(resource, struct request, resource);
if (request->length < r->length) if (request->length < r->length)
r->length = request->length; r->length = request->length;
...@@ -626,7 +667,7 @@ static int ioctl_add_descriptor(struct client *client, void *buffer) ...@@ -626,7 +667,7 @@ static int ioctl_add_descriptor(struct client *client, void *buffer)
{ {
struct fw_cdev_add_descriptor *request = buffer; struct fw_cdev_add_descriptor *request = buffer;
struct descriptor *descriptor; struct descriptor *descriptor;
int retval; int ret;
if (request->length > 256) if (request->length > 256)
return -EINVAL; return -EINVAL;
...@@ -638,8 +679,8 @@ static int ioctl_add_descriptor(struct client *client, void *buffer) ...@@ -638,8 +679,8 @@ static int ioctl_add_descriptor(struct client *client, void *buffer)
if (copy_from_user(descriptor->data, if (copy_from_user(descriptor->data,
u64_to_uptr(request->data), request->length * 4)) { u64_to_uptr(request->data), request->length * 4)) {
kfree(descriptor); ret = -EFAULT;
return -EFAULT; goto failed;
} }
descriptor->d.length = request->length; descriptor->d.length = request->length;
...@@ -647,24 +688,31 @@ static int ioctl_add_descriptor(struct client *client, void *buffer) ...@@ -647,24 +688,31 @@ static int ioctl_add_descriptor(struct client *client, void *buffer)
descriptor->d.key = request->key; descriptor->d.key = request->key;
descriptor->d.data = descriptor->data; descriptor->d.data = descriptor->data;
retval = fw_core_add_descriptor(&descriptor->d); ret = fw_core_add_descriptor(&descriptor->d);
if (retval < 0) { if (ret < 0)
kfree(descriptor); goto failed;
return retval;
}
descriptor->resource.release = release_descriptor; descriptor->resource.release = release_descriptor;
add_client_resource(client, &descriptor->resource); ret = add_client_resource(client, &descriptor->resource, GFP_KERNEL);
if (ret < 0) {
fw_core_remove_descriptor(&descriptor->d);
goto failed;
}
request->handle = descriptor->resource.handle; request->handle = descriptor->resource.handle;
return 0; return 0;
failed:
kfree(descriptor);
return ret;
} }
static int ioctl_remove_descriptor(struct client *client, void *buffer) static int ioctl_remove_descriptor(struct client *client, void *buffer)
{ {
struct fw_cdev_remove_descriptor *request = buffer; struct fw_cdev_remove_descriptor *request = buffer;
return release_client_resource(client, request->handle, NULL); return release_client_resource(client, request->handle,
release_descriptor, NULL);
} }
static void static void
...@@ -1003,11 +1051,21 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma) ...@@ -1003,11 +1051,21 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma)
return retval; return retval;
} }
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) static int fw_device_op_release(struct inode *inode, struct file *file)
{ {
struct client *client = file->private_data; struct client *client = file->private_data;
struct event *e, *next_e; struct event *e, *next_e;
struct client_resource *r, *next_r; unsigned long flags;
mutex_lock(&client->device->client_list_mutex); mutex_lock(&client->device->client_list_mutex);
list_del(&client->link); list_del(&client->link);
...@@ -1019,17 +1077,22 @@ static int fw_device_op_release(struct inode *inode, struct file *file) ...@@ -1019,17 +1077,22 @@ static int fw_device_op_release(struct inode *inode, struct file *file)
if (client->iso_context) if (client->iso_context)
fw_iso_context_destroy(client->iso_context); fw_iso_context_destroy(client->iso_context);
list_for_each_entry_safe(r, next_r, &client->resource_list, link) /* Freeze client->resource_idr and client->event_list */
r->release(client, r); spin_lock_irqsave(&client->lock, flags);
client->in_shutdown = true;
spin_unlock_irqrestore(&client->lock, flags);
/* idr_for_each(&client->resource_idr, shutdown_resource, client);
* FIXME: We should wait for the async tasklets to stop idr_remove_all(&client->resource_idr);
* running before freeing the memory. idr_destroy(&client->resource_idr);
*/
list_for_each_entry_safe(e, next_e, &client->event_list, link) list_for_each_entry_safe(e, next_e, &client->event_list, link)
kfree(e); kfree(e);
/*
* FIXME: client should be reference-counted. It's extremely unlikely
* but there may still be transactions being completed at this point.
*/
fw_device_put(client->device); fw_device_put(client->device);
kfree(client); kfree(client);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment