1778 lines · 45.5 KB · C
RawDownload
/* * 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;}; #ifdef CONFIG_COMPATstatic void __user *u64_to_uptr(u64 value){	if (is_compat_task())		return compat_ptr(value);	else		return (void __user *)(unsigned long)value;} static u64 uptr_to_u64(void __user *ptr){	if (is_compat_task())		return ptr_to_compat(ptr);	else		return (u64)(unsigned long)ptr;}#elsestatic 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;}#endif /* CONFIG_COMPAT */ 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);	INIT_LIST_HEAD(&client->link);	kref_init(&client->kref); 	file->private_data = client; 	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; 	mutex_lock(&client->device->client_list_mutex); 	client->bus_reset_closure = a->bus_reset_closure;	if (a->bus_reset != 0) {		fill_bus_reset_event(&bus_reset, client);		ret = copy_to_user(u64_to_uptr(a->bus_reset),				   &bus_reset, sizeof(bus_reset));	}	if (ret == 0 && list_empty(&client->link))		list_add_tail(&client->link, &client->device->client_list); 	mutex_unlock(&client->device->client_list_mutex); 	return ret ? -EFAULT : 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);

Lines 1–500 of 1778.