1911 lines · 49.3 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/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"#include <trace/events/firewire.h> #include "packet-header-definitions.h" /* * ABI version history is documented in linux/firewire-cdev.h. */#define FW_CDEV_KERNEL_VERSION			6#define FW_CDEV_VERSION_EVENT_REQUEST2		4#define FW_CDEV_VERSION_ALLOCATE_REGION_END	4#define FW_CDEV_VERSION_AUTO_FLUSH_ISO_OVERFLOW	5#define FW_CDEV_VERSION_EVENT_ASYNC_TSTAMP	6 static DEFINE_SPINLOCK(phy_receiver_list_lock);static LIST_HEAD(phy_receiver_list); struct client {	u32 version;	struct fw_device *device; 	spinlock_t lock;	bool in_shutdown;	struct xarray resource_xa;	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;	struct mutex iso_context_mutex;	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;	bool is_fcp;	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 struct address_handler_resource *to_address_handler_resource(struct client_resource *resource){	return container_of(resource, struct address_handler_resource, resource);} static struct inbound_transaction_resource *to_inbound_transaction_resource(struct client_resource *resource){	return container_of(resource, struct inbound_transaction_resource, resource);} static struct descriptor_resource *to_descriptor_resource(struct client_resource *resource){	return container_of(resource, struct descriptor_resource, resource);} static struct iso_resource *to_iso_resource(struct client_resource *resource){	return container_of(resource, struct iso_resource, resource);} static void release_iso_resource(struct client *, struct client_resource *); static int is_iso_resource(const struct client_resource *resource){	return resource->release == release_iso_resource;} static void release_transaction(struct client *client,				struct client_resource *resource); static int is_outbound_transaction_resource(const struct client_resource *resource){	return resource->release == release_transaction;} 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);} /* * 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;	union {		struct fw_cdev_event_response without_tstamp;		struct fw_cdev_event_response2 with_tstamp;	} rsp;}; struct inbound_transaction_event {	struct event event;	union {		struct fw_cdev_event_request request;		struct fw_cdev_event_request2 request2;		struct fw_cdev_event_request3 with_tstamp;	} 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;	union {		struct fw_cdev_event_phy_packet without_tstamp;		struct fw_cdev_event_phy_packet2 with_tstamp;	} phy_packet;}; struct inbound_phy_packet_event {	struct event event;	union {		struct fw_cdev_event_phy_packet without_tstamp;		struct fw_cdev_event_phy_packet2 with_tstamp;	} 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_obj(*client);	if (client == NULL) {		fw_device_put(device);		return -ENOMEM;	} 	client->device = device;	spin_lock_init(&client->lock);	xa_init_flags(&client->resource_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_BH);	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);	mutex_init(&client->iso_context_mutex); 	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){	event->v[0].data = data0;	event->v[0].size = size0;	event->v[1].data = data1;	event->v[1].size = size1; 	scoped_guard(spinlock_irqsave, &client->lock) {		if (client->in_shutdown)			kfree(event);		else			list_add_tail(&event->link, &client->event_list);	} 	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; 	scoped_guard(spinlock_irq, &client->lock) {		event = list_first_entry(&client->event_list, struct event, link);		list_del(&event->link);	} 	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; 	guard(spinlock_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;} static void for_each_client(struct fw_device *device,			    void (*callback)(struct client *client)){	struct client *c; 	guard(mutex)(&device->client_list_mutex); 	list_for_each_entry(c, &device->client_list, link)		callback(c);} static void queue_bus_reset_event(struct client *client){	struct bus_reset_event *e;	struct client_resource *resource;	unsigned long index; 	e = kzalloc_obj(*e);	if (e == NULL)		return; 	fill_bus_reset_event(&e->reset, client); 	queue_event(client, &e->event,		    &e->reset, sizeof(e->reset), NULL, 0); 	guard(spinlock_irq)(&client->lock); 	xa_for_each(&client->resource_xa, index, resource) {		if (is_iso_resource(resource))			schedule_iso_resource(to_iso_resource(resource), 0);	}} 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; 	scoped_guard(rwsem_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));			if (ret != 0)				return -EFAULT;		}		a->rom_length = client->device->config_rom_length * 4;	} 	guard(mutex)(&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);	}

Lines 1–500 of 1911.