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: Jay 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: Stefan Richter <stefanr@s5r6.in-berlin.de>
This commit is contained in:
committed by
Stefan Richter
parent
97811e3473
commit
45ee3199eb
@@ -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 {
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|||||||
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)
|
|||||||
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,
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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,
|
|||||||
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,
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user