/* * Char device for device raw access * * 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 * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <linux/compat.h>#include <linux/delay.h>#include <linux/device.h>#include <linux/errno.h>#include <linux/firewire.h>#include <linux/firewire-cdev.h>#include <linux/idr.h>#include <linux/jiffies.h>#include <linux/kernel.h>#include <linux/kref.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/poll.h>#include <linux/preempt.h>#include <linux/sched.h>#include <linux/spinlock.h>#include <linux/time.h>#include <linux/uaccess.h>#include <linux/vmalloc.h>#include <linux/wait.h>#include <linux/workqueue.h> #include <asm/system.h> #include "core.h" struct client { u32 version; struct fw_device *device; spinlock_t lock; bool in_shutdown; struct idr resource_idr; struct list_head event_list; wait_queue_head_t wait; u64 bus_reset_closure; struct fw_iso_context *iso_context; u64 iso_closure; struct fw_iso_buffer buffer; unsigned long vm_start; struct list_head link; struct kref kref;}; static inline void client_get(struct client *client){ kref_get(&client->kref);} static void client_release(struct kref *kref){ struct client *client = container_of(kref, struct client, kref); fw_device_put(client->device); kfree(client);} static void client_put(struct client *client){ kref_put(&client->kref, client_release);} struct client_resource;typedef void (*client_resource_release_fn_t)(struct client *, struct client_resource *);struct client_resource { client_resource_release_fn_t release; int handle;}; struct address_handler_resource { struct client_resource resource; struct fw_address_handler handler; __u64 closure; struct client *client;}; struct outbound_transaction_resource { struct client_resource resource; struct fw_transaction transaction;}; struct inbound_transaction_resource { struct client_resource resource; struct fw_request *request; void *data; size_t length;}; struct descriptor_resource { struct client_resource resource; struct fw_descriptor descriptor; u32 data[0];}; struct iso_resource { struct client_resource resource; struct client *client; /* Schedule work and access todo only with client->lock held. */ struct delayed_work work; enum {ISO_RES_ALLOC, ISO_RES_REALLOC, ISO_RES_DEALLOC, ISO_RES_ALLOC_ONCE, ISO_RES_DEALLOC_ONCE,} todo; int generation; u64 channels; s32 bandwidth; __be32 transaction_data[2]; struct iso_resource_event *e_alloc, *e_dealloc;}; static void schedule_iso_resource(struct iso_resource *);static void release_iso_resource(struct client *, struct client_resource *); /* * dequeue_event() just kfree()'s the event, so the event has to be * the first field in a struct XYZ_event. */struct event { struct { void *data; size_t size; } v[2]; struct list_head link;}; struct bus_reset_event { struct event event; struct fw_cdev_event_bus_reset reset;}; struct outbound_transaction_event { struct event event; struct client *client; struct outbound_transaction_resource r; struct fw_cdev_event_response response;}; struct inbound_transaction_event { struct event event; struct fw_cdev_event_request request;}; struct iso_interrupt_event { struct event event; struct fw_cdev_event_iso_interrupt interrupt;}; struct iso_resource_event { struct event event; struct fw_cdev_event_iso_resource resource;}; static inline void __user *u64_to_uptr(__u64 value){ return (void __user *)(unsigned long)value;} static inline __u64 uptr_to_u64(void __user *ptr){ return (__u64)(unsigned long)ptr;} static int fw_device_op_open(struct inode *inode, struct file *file){ struct fw_device *device; struct client *client; device = fw_device_get_by_devt(inode->i_rdev); if (device == NULL) return -ENODEV; if (fw_device_is_shutdown(device)) { fw_device_put(device); return -ENODEV; } client = kzalloc(sizeof(*client), GFP_KERNEL); if (client == NULL) { fw_device_put(device); return -ENOMEM; } client->device = device; spin_lock_init(&client->lock); idr_init(&client->resource_idr); INIT_LIST_HEAD(&client->event_list); init_waitqueue_head(&client->wait); kref_init(&client->kref); file->private_data = client; mutex_lock(&device->client_list_mutex); list_add_tail(&client->link, &device->client_list); mutex_unlock(&device->client_list_mutex); return 0;} static void queue_event(struct client *client, struct event *event, void *data0, size_t size0, void *data1, size_t size1){ unsigned long flags; event->v[0].data = data0; event->v[0].size = size0; event->v[1].data = data1; event->v[1].size = size1; spin_lock_irqsave(&client->lock, flags); if (client->in_shutdown) kfree(event); else list_add_tail(&event->link, &client->event_list); spin_unlock_irqrestore(&client->lock, flags); wake_up_interruptible(&client->wait);} static int dequeue_event(struct client *client, char __user *buffer, size_t count){ struct event *event; size_t size, total; int i, ret; ret = wait_event_interruptible(client->wait, !list_empty(&client->event_list) || fw_device_is_shutdown(client->device)); if (ret < 0) return ret; if (list_empty(&client->event_list) && fw_device_is_shutdown(client->device)) return -ENODEV; spin_lock_irq(&client->lock); event = list_first_entry(&client->event_list, struct event, link); list_del(&event->link); spin_unlock_irq(&client->lock); total = 0; for (i = 0; i < ARRAY_SIZE(event->v) && total < count; i++) { size = min(event->v[i].size, count - total); if (copy_to_user(buffer + total, event->v[i].data, size)) { ret = -EFAULT; goto out; } total += size; } ret = total; out: kfree(event); return ret;} static ssize_t fw_device_op_read(struct file *file, char __user *buffer, size_t count, loff_t *offset){ struct client *client = file->private_data; return dequeue_event(client, buffer, count);} static void fill_bus_reset_event(struct fw_cdev_event_bus_reset *event, struct client *client){ struct fw_card *card = client->device->card; spin_lock_irq(&card->lock); event->closure = client->bus_reset_closure; event->type = FW_CDEV_EVENT_BUS_RESET; event->generation = client->device->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; spin_unlock_irq(&card->lock);} static void for_each_client(struct fw_device *device, void (*callback)(struct client *client)){ struct client *c; mutex_lock(&device->client_list_mutex); list_for_each_entry(c, &device->client_list, link) callback(c); mutex_unlock(&device->client_list_mutex);} static int schedule_reallocations(int id, void *p, void *data){ struct client_resource *r = p; if (r->release == release_iso_resource) schedule_iso_resource(container_of(r, struct iso_resource, resource)); return 0;} static void queue_bus_reset_event(struct client *client){ struct bus_reset_event *e; e = kzalloc(sizeof(*e), GFP_KERNEL); if (e == NULL) { fw_notify("Out of memory when allocating bus reset event\n"); return; } fill_bus_reset_event(&e->reset, client); queue_event(client, &e->event, &e->reset, sizeof(e->reset), NULL, 0); spin_lock_irq(&client->lock); idr_for_each(&client->resource_idr, schedule_reallocations, client); spin_unlock_irq(&client->lock);} void fw_device_cdev_update(struct fw_device *device){ for_each_client(device, queue_bus_reset_event);} static void wake_up_client(struct client *client){ wake_up_interruptible(&client->wait);} void fw_device_cdev_remove(struct fw_device *device){ for_each_client(device, wake_up_client);} 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; get_info->card = client->device->card->index; 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; 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))) return -EFAULT; } return 0;} static int add_client_resource(struct client *client, struct client_resource *resource, gfp_t gfp_mask){ unsigned long flags; int ret; retry: if (idr_pre_get(&client->resource_idr, gfp_mask) == 0) return -ENOMEM; spin_lock_irqsave(&client->lock, flags); if (client->in_shutdown) ret = -ECANCELED; else ret = idr_get_new(&client->resource_idr, resource, &resource->handle); if (ret >= 0) { client_get(client); if (resource->release == release_iso_resource) schedule_iso_resource(container_of(resource, struct iso_resource, resource)); } spin_unlock_irqrestore(&client->lock, flags); if (ret == -EAGAIN) goto retry; return ret < 0 ? ret : 0;} static int release_client_resource(struct client *client, u32 handle, client_resource_release_fn_t release, struct client_resource **resource){ struct client_resource *r; spin_lock_irq(&client->lock); if (client->in_shutdown) r = NULL; else r = idr_find(&client->resource_idr, handle); if (r && r->release == release) idr_remove(&client->resource_idr, handle); spin_unlock_irq(&client->lock); if (!(r && r->release == release)) return -EINVAL; if (resource) *resource = r; else r->release(client, r); client_put(client); return 0;} static void release_transaction(struct client *client, struct client_resource *resource){ struct outbound_transaction_resource *r = container_of(resource, struct outbound_transaction_resource, resource); fw_cancel_transaction(client->device->card, &r->transaction);} static void complete_transaction(struct fw_card *card, int rcode, void *payload, size_t length, void *data){ struct outbound_transaction_event *e = data; struct fw_cdev_event_response *rsp = &e->response; struct client *client = e->client; unsigned long flags; if (length < rsp->length) rsp->length = length; if (rcode == RCODE_COMPLETE) memcpy(rsp->data, payload, rsp->length); spin_lock_irqsave(&client->lock, flags); /* * 1. If called while in shutdown, the idr tree must be left untouched. * The idr handle will be removed and the client reference will be * dropped later. * 2. If the call chain was release_client_resource -> * release_transaction -> complete_transaction (instead of a normal * conclusion of the transaction), i.e. if this resource was already * unregistered from the idr, the client reference will be dropped * by release_client_resource and we must not drop it here. */ if (!client->in_shutdown && idr_find(&client->resource_idr, e->r.resource.handle)) { idr_remove(&client->resource_idr, e->r.resource.handle); /* Drop the idr's reference */ client_put(client); } spin_unlock_irqrestore(&client->lock, flags); rsp->type = FW_CDEV_EVENT_RESPONSE; rsp->rcode = rcode; Lines 1–500 of 1461. Load more
/* * Char device for device raw access * * 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 * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <linux/compat.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/errno.h> #include <linux/firewire.h> #include <linux/firewire-cdev.h> #include <linux/idr.h> #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/kref.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/poll.h> #include <linux/preempt.h> #include <linux/sched.h> #include <linux/spinlock.h> #include <linux/time.h> #include <linux/uaccess.h> #include <linux/vmalloc.h> #include <linux/wait.h> #include <linux/workqueue.h> #include <asm/system.h> #include "core.h" struct client { u32 version; struct fw_device *device; spinlock_t lock; bool in_shutdown; struct idr resource_idr; struct list_head event_list; wait_queue_head_t wait; u64 bus_reset_closure; struct fw_iso_context *iso_context; u64 iso_closure; struct fw_iso_buffer buffer; unsigned long vm_start; struct list_head link; struct kref kref; }; static inline void client_get(struct client *client) { kref_get(&client->kref); } static void client_release(struct kref *kref) { struct client *client = container_of(kref, struct client, kref); fw_device_put(client->device); kfree(client); } static void client_put(struct client *client) { kref_put(&client->kref, client_release); } struct client_resource; typedef void (*client_resource_release_fn_t)(struct client *, struct client_resource *); struct client_resource { client_resource_release_fn_t release; int handle; }; struct address_handler_resource { struct client_resource resource; struct fw_address_handler handler; __u64 closure; struct client *client; }; struct outbound_transaction_resource { struct client_resource resource; struct fw_transaction transaction; }; struct inbound_transaction_resource { struct client_resource resource; struct fw_request *request; void *data; size_t length; }; struct descriptor_resource { struct client_resource resource; struct fw_descriptor descriptor; u32 data[0]; }; struct iso_resource { struct client_resource resource; struct client *client; /* Schedule work and access todo only with client->lock held. */ struct delayed_work work; enum {ISO_RES_ALLOC, ISO_RES_REALLOC, ISO_RES_DEALLOC, ISO_RES_ALLOC_ONCE, ISO_RES_DEALLOC_ONCE,} todo; int generation; u64 channels; s32 bandwidth; __be32 transaction_data[2]; struct iso_resource_event *e_alloc, *e_dealloc; }; static void schedule_iso_resource(struct iso_resource *); static void release_iso_resource(struct client *, struct client_resource *); /* * dequeue_event() just kfree()'s the event, so the event has to be * the first field in a struct XYZ_event. */ struct event { struct { void *data; size_t size; } v[2]; struct list_head link; }; struct bus_reset_event { struct event event; struct fw_cdev_event_bus_reset reset; }; struct outbound_transaction_event { struct event event; struct client *client; struct outbound_transaction_resource r; struct fw_cdev_event_response response; }; struct inbound_transaction_event { struct event event; struct fw_cdev_event_request request; }; struct iso_interrupt_event { struct event event; struct fw_cdev_event_iso_interrupt interrupt; }; struct iso_resource_event { struct event event; struct fw_cdev_event_iso_resource resource; }; static inline void __user *u64_to_uptr(__u64 value) { return (void __user *)(unsigned long)value; } static inline __u64 uptr_to_u64(void __user *ptr) { return (__u64)(unsigned long)ptr; } static int fw_device_op_open(struct inode *inode, struct file *file) { struct fw_device *device; struct client *client; device = fw_device_get_by_devt(inode->i_rdev); if (device == NULL) return -ENODEV; if (fw_device_is_shutdown(device)) { fw_device_put(device); return -ENODEV; } client = kzalloc(sizeof(*client), GFP_KERNEL); if (client == NULL) { fw_device_put(device); return -ENOMEM; } client->device = device; spin_lock_init(&client->lock); idr_init(&client->resource_idr); INIT_LIST_HEAD(&client->event_list); init_waitqueue_head(&client->wait); kref_init(&client->kref); file->private_data = client; mutex_lock(&device->client_list_mutex); list_add_tail(&client->link, &device->client_list); mutex_unlock(&device->client_list_mutex); return 0; } static void queue_event(struct client *client, struct event *event, void *data0, size_t size0, void *data1, size_t size1) { unsigned long flags; event->v[0].data = data0; event->v[0].size = size0; event->v[1].data = data1; event->v[1].size = size1; spin_lock_irqsave(&client->lock, flags); if (client->in_shutdown) kfree(event); else list_add_tail(&event->link, &client->event_list); spin_unlock_irqrestore(&client->lock, flags); wake_up_interruptible(&client->wait); } static int dequeue_event(struct client *client, char __user *buffer, size_t count) { struct event *event; size_t size, total; int i, ret; ret = wait_event_interruptible(client->wait, !list_empty(&client->event_list) || fw_device_is_shutdown(client->device)); if (ret < 0) return ret; if (list_empty(&client->event_list) && fw_device_is_shutdown(client->device)) return -ENODEV; spin_lock_irq(&client->lock); event = list_first_entry(&client->event_list, struct event, link); list_del(&event->link); spin_unlock_irq(&client->lock); total = 0; for (i = 0; i < ARRAY_SIZE(event->v) && total < count; i++) { size = min(event->v[i].size, count - total); if (copy_to_user(buffer + total, event->v[i].data, size)) { ret = -EFAULT; goto out; } total += size; } ret = total; out: kfree(event); return ret; } static ssize_t fw_device_op_read(struct file *file, char __user *buffer, size_t count, loff_t *offset) { struct client *client = file->private_data; return dequeue_event(client, buffer, count); } static void fill_bus_reset_event(struct fw_cdev_event_bus_reset *event, struct client *client) { struct fw_card *card = client->device->card; spin_lock_irq(&card->lock); event->closure = client->bus_reset_closure; event->type = FW_CDEV_EVENT_BUS_RESET; event->generation = client->device->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; spin_unlock_irq(&card->lock); } static void for_each_client(struct fw_device *device, void (*callback)(struct client *client)) { struct client *c; mutex_lock(&device->client_list_mutex); list_for_each_entry(c, &device->client_list, link) callback(c); mutex_unlock(&device->client_list_mutex); } static int schedule_reallocations(int id, void *p, void *data) { struct client_resource *r = p; if (r->release == release_iso_resource) schedule_iso_resource(container_of(r, struct iso_resource, resource)); return 0; } static void queue_bus_reset_event(struct client *client) { struct bus_reset_event *e; e = kzalloc(sizeof(*e), GFP_KERNEL); if (e == NULL) { fw_notify("Out of memory when allocating bus reset event\n"); return; } fill_bus_reset_event(&e->reset, client); queue_event(client, &e->event, &e->reset, sizeof(e->reset), NULL, 0); spin_lock_irq(&client->lock); idr_for_each(&client->resource_idr, schedule_reallocations, client); spin_unlock_irq(&client->lock); } void fw_device_cdev_update(struct fw_device *device) { for_each_client(device, queue_bus_reset_event); } static void wake_up_client(struct client *client) { wake_up_interruptible(&client->wait); } void fw_device_cdev_remove(struct fw_device *device) { for_each_client(device, wake_up_client); } 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; get_info->card = client->device->card->index; 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; 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))) return -EFAULT; } return 0; } static int add_client_resource(struct client *client, struct client_resource *resource, gfp_t gfp_mask) { unsigned long flags; int ret; retry: if (idr_pre_get(&client->resource_idr, gfp_mask) == 0) return -ENOMEM; spin_lock_irqsave(&client->lock, flags); if (client->in_shutdown) ret = -ECANCELED; else ret = idr_get_new(&client->resource_idr, resource, &resource->handle); if (ret >= 0) { client_get(client); if (resource->release == release_iso_resource) schedule_iso_resource(container_of(resource, struct iso_resource, resource)); } spin_unlock_irqrestore(&client->lock, flags); if (ret == -EAGAIN) goto retry; return ret < 0 ? ret : 0; } static int release_client_resource(struct client *client, u32 handle, client_resource_release_fn_t release, struct client_resource **resource) { struct client_resource *r; spin_lock_irq(&client->lock); if (client->in_shutdown) r = NULL; else r = idr_find(&client->resource_idr, handle); if (r && r->release == release) idr_remove(&client->resource_idr, handle); spin_unlock_irq(&client->lock); if (!(r && r->release == release)) return -EINVAL; if (resource) *resource = r; else r->release(client, r); client_put(client); return 0; } static void release_transaction(struct client *client, struct client_resource *resource) { struct outbound_transaction_resource *r = container_of(resource, struct outbound_transaction_resource, resource); fw_cancel_transaction(client->device->card, &r->transaction); } static void complete_transaction(struct fw_card *card, int rcode, void *payload, size_t length, void *data) { struct outbound_transaction_event *e = data; struct fw_cdev_event_response *rsp = &e->response; struct client *client = e->client; unsigned long flags; if (length < rsp->length) rsp->length = length; if (rcode == RCODE_COMPLETE) memcpy(rsp->data, payload, rsp->length); spin_lock_irqsave(&client->lock, flags); /* * 1. If called while in shutdown, the idr tree must be left untouched. * The idr handle will be removed and the client reference will be * dropped later. * 2. If the call chain was release_client_resource -> * release_transaction -> complete_transaction (instead of a normal * conclusion of the transaction), i.e. if this resource was already * unregistered from the idr, the client reference will be dropped * by release_client_resource and we must not drop it here. */ if (!client->in_shutdown && idr_find(&client->resource_idr, e->r.resource.handle)) { idr_remove(&client->resource_idr, e->r.resource.handle); /* Drop the idr's reference */ client_put(client); } spin_unlock_irqrestore(&client->lock, flags); rsp->type = FW_CDEV_EVENT_RESPONSE; rsp->rcode = rcode;