/* * 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/bug.h>#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/irqflags.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/sched.h> /* required for linux/wait.h */#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/string.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" /* * ABI version history is documented in linux/firewire-cdev.h. */#define FW_CDEV_KERNEL_VERSION 4#define FW_CDEV_VERSION_EVENT_REQUEST2 4#define FW_CDEV_VERSION_ALLOCATE_REGION_END 4 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; wait_queue_head_t tx_flush_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 phy_receiver_link; u64 phy_receiver_closure; 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_card *card; 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; struct iso_resource_event *e_alloc, *e_dealloc;}; static void release_iso_resource(struct client *, struct client_resource *); static void schedule_iso_resource(struct iso_resource *r, unsigned long delay){ client_get(r->client); if (!queue_delayed_work(fw_workqueue, &r->work, delay)) client_put(r->client);} static void schedule_if_iso_resource(struct client_resource *resource){ if (resource->release == release_iso_resource) schedule_iso_resource(container_of(resource, struct iso_resource, resource), 0);} /* * 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; union { struct fw_cdev_event_request request; struct fw_cdev_event_request2 request2; } req;}; struct iso_interrupt_event { struct event event; struct fw_cdev_event_iso_interrupt interrupt;}; struct iso_interrupt_mc_event { struct event event; struct fw_cdev_event_iso_interrupt_mc interrupt;}; struct iso_resource_event { struct event event; struct fw_cdev_event_iso_resource iso_resource;}; struct outbound_phy_packet_event { struct event event; struct client *client; struct fw_packet p; struct fw_cdev_event_phy_packet phy_packet;}; struct inbound_phy_packet_event { struct event event; struct fw_cdev_event_phy_packet phy_packet;}; 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); init_waitqueue_head(&client->tx_flush_wait); INIT_LIST_HEAD(&client->phy_receiver_link); 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 nonseekable_open(inode, file);} 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 = card->bm_node_id; 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){ schedule_if_iso_resource(p); 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 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);} union ioctl_arg { struct fw_cdev_get_info get_info; struct fw_cdev_send_request send_request; struct fw_cdev_allocate allocate; struct fw_cdev_deallocate deallocate; struct fw_cdev_send_response send_response; struct fw_cdev_initiate_bus_reset initiate_bus_reset; struct fw_cdev_add_descriptor add_descriptor; struct fw_cdev_remove_descriptor remove_descriptor; struct fw_cdev_create_iso_context create_iso_context; struct fw_cdev_queue_iso queue_iso; struct fw_cdev_start_iso start_iso; struct fw_cdev_stop_iso stop_iso; struct fw_cdev_get_cycle_timer get_cycle_timer; struct fw_cdev_allocate_iso_resource allocate_iso_resource; struct fw_cdev_send_stream_packet send_stream_packet; struct fw_cdev_get_cycle_timer2 get_cycle_timer2; struct fw_cdev_send_phy_packet send_phy_packet; struct fw_cdev_receive_phy_packets receive_phy_packets; struct fw_cdev_set_iso_channels set_iso_channels;}; static int ioctl_get_info(struct client *client, union ioctl_arg *arg){ struct fw_cdev_get_info *a = &arg->get_info; struct fw_cdev_event_bus_reset bus_reset; unsigned long ret = 0; client->version = a->version; a->version = FW_CDEV_KERNEL_VERSION; a->card = client->device->card->index; down_read(&fw_device_rwsem); if (a->rom != 0) { size_t want = a->rom_length; size_t have = client->device->config_rom_length * 4; ret = copy_to_user(u64_to_uptr(a->rom), client->device->config_rom, min(want, have)); } a->rom_length = client->device->config_rom_length * 4; up_read(&fw_device_rwsem); if (ret != 0) return -EFAULT; client->bus_reset_closure = a->bus_reset_closure; if (a->bus_reset != 0) { fill_bus_reset_event(&bus_reset, client); if (copy_to_user(u64_to_uptr(a->bus_reset), &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); schedule_if_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 **return_resource){ struct client_resource *resource; spin_lock_irq(&client->lock); if (client->in_shutdown)Lines 1–500 of 1758. 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/bug.h> #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/irqflags.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/sched.h> /* required for linux/wait.h */ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/string.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" /* * ABI version history is documented in linux/firewire-cdev.h. */ #define FW_CDEV_KERNEL_VERSION 4 #define FW_CDEV_VERSION_EVENT_REQUEST2 4 #define FW_CDEV_VERSION_ALLOCATE_REGION_END 4 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; wait_queue_head_t tx_flush_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 phy_receiver_link; u64 phy_receiver_closure; 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_card *card; 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; struct iso_resource_event *e_alloc, *e_dealloc; }; static void release_iso_resource(struct client *, struct client_resource *); static void schedule_iso_resource(struct iso_resource *r, unsigned long delay) { client_get(r->client); if (!queue_delayed_work(fw_workqueue, &r->work, delay)) client_put(r->client); } static void schedule_if_iso_resource(struct client_resource *resource) { if (resource->release == release_iso_resource) schedule_iso_resource(container_of(resource, struct iso_resource, resource), 0); } /* * 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; union { struct fw_cdev_event_request request; struct fw_cdev_event_request2 request2; } req; }; struct iso_interrupt_event { struct event event; struct fw_cdev_event_iso_interrupt interrupt; }; struct iso_interrupt_mc_event { struct event event; struct fw_cdev_event_iso_interrupt_mc interrupt; }; struct iso_resource_event { struct event event; struct fw_cdev_event_iso_resource iso_resource; }; struct outbound_phy_packet_event { struct event event; struct client *client; struct fw_packet p; struct fw_cdev_event_phy_packet phy_packet; }; struct inbound_phy_packet_event { struct event event; struct fw_cdev_event_phy_packet phy_packet; }; 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); init_waitqueue_head(&client->tx_flush_wait); INIT_LIST_HEAD(&client->phy_receiver_link); 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 nonseekable_open(inode, file); } 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 = card->bm_node_id; 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) { schedule_if_iso_resource(p); 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 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); } union ioctl_arg { struct fw_cdev_get_info get_info; struct fw_cdev_send_request send_request; struct fw_cdev_allocate allocate; struct fw_cdev_deallocate deallocate; struct fw_cdev_send_response send_response; struct fw_cdev_initiate_bus_reset initiate_bus_reset; struct fw_cdev_add_descriptor add_descriptor; struct fw_cdev_remove_descriptor remove_descriptor; struct fw_cdev_create_iso_context create_iso_context; struct fw_cdev_queue_iso queue_iso; struct fw_cdev_start_iso start_iso; struct fw_cdev_stop_iso stop_iso; struct fw_cdev_get_cycle_timer get_cycle_timer; struct fw_cdev_allocate_iso_resource allocate_iso_resource; struct fw_cdev_send_stream_packet send_stream_packet; struct fw_cdev_get_cycle_timer2 get_cycle_timer2; struct fw_cdev_send_phy_packet send_phy_packet; struct fw_cdev_receive_phy_packets receive_phy_packets; struct fw_cdev_set_iso_channels set_iso_channels; }; static int ioctl_get_info(struct client *client, union ioctl_arg *arg) { struct fw_cdev_get_info *a = &arg->get_info; struct fw_cdev_event_bus_reset bus_reset; unsigned long ret = 0; client->version = a->version; a->version = FW_CDEV_KERNEL_VERSION; a->card = client->device->card->index; down_read(&fw_device_rwsem); if (a->rom != 0) { size_t want = a->rom_length; size_t have = client->device->config_rom_length * 4; ret = copy_to_user(u64_to_uptr(a->rom), client->device->config_rom, min(want, have)); } a->rom_length = client->device->config_rom_length * 4; up_read(&fw_device_rwsem); if (ret != 0) return -EFAULT; client->bus_reset_closure = a->bus_reset_closure; if (a->bus_reset != 0) { fill_bus_reset_event(&bus_reset, client); if (copy_to_user(u64_to_uptr(a->bus_reset), &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); schedule_if_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 **return_resource) { struct client_resource *resource; spin_lock_irq(&client->lock); if (client->in_shutdown)