// 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 1810. Load more
// 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_COMPAT static 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; } #else 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; } #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;