1814 lines · 45.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Char device for device raw access * * Copyright (C) 2005-2007  Kristian Hoegsberg <krh@bitplanet.net> */ #include <linux/bug.h>#include <linux/compat.h>#include <linux/delay.h>#include <linux/device.h>#include <linux/dma-mapping.h>#include <linux/err.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 "core.h" /* * ABI version history is documented in linux/firewire-cdev.h. */#define FW_CDEV_KERNEL_VERSION			5#define FW_CDEV_VERSION_EVENT_REQUEST2		4#define FW_CDEV_VERSION_ALLOCATE_REGION_END	4#define FW_CDEV_VERSION_AUTO_FLUSH_ISO_OVERFLOW	5 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;	bool buffer_is_mapped; 	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[];}; 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 (in_compat_syscall())		return compat_ptr(value);	else		return (void __user *)(unsigned long)value;} static u64 uptr_to_u64(void __user *ptr){	if (in_compat_syscall())		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)		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;	struct fw_cdev_flush_iso		flush_iso;}; 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);		/* unaligned size of bus_reset is 36 bytes */		ret = copy_to_user(u64_to_uptr(a->bus_reset), &bus_reset, 36);	}	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){	bool preload = gfpflags_allow_blocking(gfp_mask);	unsigned long flags;	int ret; 	if (preload)		idr_preload(gfp_mask);	spin_lock_irqsave(&client->lock, flags); 	if (client->in_shutdown)		ret = -ECANCELED;	else		ret = idr_alloc(&client->resource_idr, resource, 0, 0,				GFP_NOWAIT);	if (ret >= 0) {		resource->handle = ret;		client_get(client);		schedule_if_iso_resource(resource);	} 	spin_unlock_irqrestore(&client->lock, flags);	if (preload)		idr_preload_end(); 	return ret < 0 ? ret : 0;

Lines 1–500 of 1814.