|
|
/* | /* |
* | * |
* Copyright (c) 2003 The Regents of the University of California. All |
* Copyright (c) 2003 The Regents of the University of California. All |
* rights reserved. | * rights reserved. |
* | * |
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without |
|
|
* are met: | * are met: |
* | * |
* - Redistributions of source code must retain the above copyright | * - Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* notice, this list of conditions and the following disclaimer. |
* | * |
* - Neither the name of the University nor the names of its | * - Neither the name of the University nor the names of its |
* contributors may be used to endorse or promote products derived |
* contributors may be used to endorse or promote products derived |
* from this software without specific prior written permission. |
* from this software without specific prior written permission. |
* | * |
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' |
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR |
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR |
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
* | * |
*/ | */ |
|
|
| |
/* | /* |
* FUSD: the Framework for User-Space Devices | * FUSD: the Framework for User-Space Devices |
* | * |
* Linux Kernel Module | * Linux Kernel Module |
* | * |
* Jeremy Elson <jelson@circlemud.org> |
* Jeremy Elson <jelson@circlemud.org> |
* Copyright (c) 2001, Sensoria Corporation | * Copyright (c) 2001, Sensoria Corporation |
* Copyright (c) 2002-2003, Regents of the University of California | * Copyright (c) 2002-2003, Regents of the University of California |
* | * |
|
|
| |
/* | /* |
* Note on debugging messages: Unexpected errors (i.e., indicators of | * Note on debugging messages: Unexpected errors (i.e., indicators of |
* bugs in this kernel module) should always contain '!'. Expected |
* bugs in this kernel module) should always contain '!'. Expected |
* conditions, even if exceptional (e.g., the device-driver-provider | * conditions, even if exceptional (e.g., the device-driver-provider |
* disappears while a file is waiting for a return from a system call) | * disappears while a file is waiting for a return from a system call) |
* must NOT contain '!'. | * must NOT contain '!'. |
|
|
#include <linux/modversions.h> | #include <linux/modversions.h> |
#endif | #endif |
| |
#include <linux/config.h> |
//#include <linux/config.h> |
#include <linux/stddef.h> | #include <linux/stddef.h> |
#include <linux/kernel.h> | #include <linux/kernel.h> |
#include <linux/module.h> | #include <linux/module.h> |
|
|
#include <linux/mm.h> | #include <linux/mm.h> |
#include <linux/slab.h> | #include <linux/slab.h> |
#include <linux/vmalloc.h> | #include <linux/vmalloc.h> |
#include <linux/devfs_fs_kernel.h> |
//#include <linux/devfs_fs_kernel.h> |
#include <linux/poll.h> | #include <linux/poll.h> |
#include <linux/version.h> | #include <linux/version.h> |
#include <linux/major.h> | #include <linux/major.h> |
|
|
/* Define this if you want to emit debug messages (adds ~8K) */ | /* Define this if you want to emit debug messages (adds ~8K) */ |
#define CONFIG_FUSD_DEBUG | #define CONFIG_FUSD_DEBUG |
| |
/* Default debug level for FUSD messages. Has no effect unless |
/* Default debug level for FUSD messages. Has no effect unless |
* CONFIG_FUSD_DEBUG is defined. */ | * CONFIG_FUSD_DEBUG is defined. */ |
#ifndef CONFIG_FUSD_DEBUGLEVEL | #ifndef CONFIG_FUSD_DEBUGLEVEL |
#define CONFIG_FUSD_DEBUGLEVEL 2 |
#define CONFIG_FUSD_DEBUGLEVEL 10 |
#endif | #endif |
| |
/* Define this to check for memory leaks */ | /* Define this to check for memory leaks */ |
/*#define CONFIG_FUSD_MEMDEBUG*/ | /*#define CONFIG_FUSD_MEMDEBUG*/ |
| |
/* Define this to use the faster wake_up_interruptible_sync instead of | /* Define this to use the faster wake_up_interruptible_sync instead of |
* the normal wake_up_interruptible. Note: you can't do this unless |
* the normal wake_up_interruptible. Note: you can't do this unless |
* you're bulding fusd as part of the kernel (not a module); or you've | * you're bulding fusd as part of the kernel (not a module); or you've |
* patched kernel/ksyms.s to add __wake_up_sync in addition to | * patched kernel/ksyms.s to add __wake_up_sync in addition to |
* __wake_up. */ | * __wake_up. */ |
|
|
| |
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,9) | #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,9) |
# define vsnprintf(str, size, format, ap) vsprintf(str, format, ap) | # define vsnprintf(str, size, format, ap) vsprintf(str, format, ap) |
# define snprintf(str, len, args...) sprintf(str, args) |
# define snprintf(str, len, args...) sprintf(str, args) |
#endif | #endif |
| |
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) | #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) |
|
|
#ifdef CONFIG_FUSD_DEBUG | #ifdef CONFIG_FUSD_DEBUG |
| |
STATIC int fusd_debug_level = CONFIG_FUSD_DEBUGLEVEL; | STATIC int fusd_debug_level = CONFIG_FUSD_DEBUGLEVEL; |
MODULE_PARM(fusd_debug_level, "i"); |
MODULE_PARM_DESC(fusd_debug_level, "i"); |
| |
#define BUFSIZE 1000 /* kernel's kmalloc pool has a 1012-sized bucket */ | #define BUFSIZE 1000 /* kernel's kmalloc pool has a 1012-sized bucket */ |
| |
STATIC void rdebug_real(char *fmt, ...) | STATIC void rdebug_real(char *fmt, ...) |
{ | { |
va_list ap; |
va_list ap; |
int len; |
int len; |
char *message; |
char *message; |
|
|
/* I'm kmallocing since you don't really want 1k on the stack. I've |
/* I'm kmallocing since you don't really want 1k on the stack. I've |
* had stack overflow problems before; the kernel stack is quite |
* had stack overflow problems before; the kernel stack is quite |
* small... */ |
* small... */ |
if ((message = KMALLOC(BUFSIZE, GFP_KERNEL)) == NULL) |
if ((message = KMALLOC(BUFSIZE, GFP_KERNEL)) == NULL) |
return; |
return; |
|
|
va_start(ap, fmt); |
va_start(ap, fmt); |
len = vsnprintf(message, BUFSIZE-1, fmt, ap); |
len = vsnprintf(message, BUFSIZE-1, fmt, ap); |
va_end(ap); |
va_end(ap); |
|
|
if (len >= BUFSIZE) { |
if (len >= BUFSIZE) { |
printk("WARNING: POSSIBLE KERNEL CORRUPTION; MESSAGE TOO LONG\n"); |
printk("WARNING: POSSIBLE KERNEL CORRUPTION; MESSAGE TOO LONG\n"); |
} else { |
} else { |
printk("fusd: %.975s\n", message); /* note msgs are truncated at |
printk("fusd: %.975s\n", message); /* note msgs are truncated at |
* ~1000 chars to fit inside the 1024 printk |
* ~1000 chars to fit inside the 1024 printk |
* limit imposed by the kernel */ |
* limit imposed by the kernel */ |
} |
} |
| |
KFREE(message); |
KFREE(message); |
} | } |
| |
#endif /* CONFIG_FUSD_DEBUG */ | #endif /* CONFIG_FUSD_DEBUG */ |
|
|
DECLARE_MUTEX(fusd_memdebug_sem); | DECLARE_MUTEX(fusd_memdebug_sem); |
| |
typedef struct { | typedef struct { |
void *ptr; |
void *ptr; |
int line; |
int line; |
int size; |
int size; |
} mem_debug_t; | } mem_debug_t; |
| |
mem_debug_t *mem_debug; | mem_debug_t *mem_debug; |
| |
STATIC int fusd_mem_init(void) | STATIC int fusd_mem_init(void) |
{ | { |
int i; |
int i; |
| |
mem_debug = kmalloc(sizeof(mem_debug_t) * MAX_MEM_DEBUG, GFP_KERNEL); |
mem_debug = kmalloc(sizeof(mem_debug_t) * MAX_MEM_DEBUG, GFP_KERNEL); |
| |
if (mem_debug == NULL) { |
if (mem_debug == NULL) { |
RDEBUG(2, "argh - memdebug malloc failed!"); |
RDEBUG(2, "argh - memdebug malloc failed!"); |
return -ENOMEM; |
return -ENOMEM; |
} |
} |
| |
/* initialize */ |
/* initialize */ |
for (i = 0; i < MAX_MEM_DEBUG; i++) |
for (i = 0; i < MAX_MEM_DEBUG; i++) |
mem_debug[i].ptr = NULL; |
mem_debug[i].ptr = NULL; |
| |
RDEBUG(2, "FUSD memory debugger activated"); |
RDEBUG(2, "FUSD memory debugger activated"); |
return 0; |
return 0; |
} | } |
| |
STATIC void fusd_mem_cleanup(void) | STATIC void fusd_mem_cleanup(void) |
{ | { |
int i; |
int i; |
int count=0; |
int count=0; |
for (i = 0; i < MAX_MEM_DEBUG; i++) |
for (i = 0; i < MAX_MEM_DEBUG; i++) |
if (mem_debug[i].ptr != NULL) { |
if (mem_debug[i].ptr != NULL) { |
RDEBUG(0, "memdebug: failed to free memory allocated at line %d (%d b)", |
RDEBUG(0, "memdebug: failed to free memory allocated at line %d (%d b)", |
mem_debug[i].line, mem_debug[i].size); |
mem_debug[i].line, mem_debug[i].size); |
count++; |
count++; |
} |
} |
if (!count) |
if (!count) |
RDEBUG(2, "congratulations - memory debugger is happy!"); |
RDEBUG(2, "congratulations - memory debugger is happy!"); |
kfree(mem_debug); |
kfree(mem_debug); |
} | } |
| |
STATIC void fusd_mem_add(void *ptr, int line, int size) | STATIC void fusd_mem_add(void *ptr, int line, int size) |
{ | { |
int i; |
int i; |
| |
if (ptr==NULL) |
if (ptr==NULL) |
return; |
return; |
| |
for (i = 0; i < MAX_MEM_DEBUG; i++) { |
for (i = 0; i < MAX_MEM_DEBUG; i++) { |
if (mem_debug[i].ptr == NULL) { |
if (mem_debug[i].ptr == NULL) { |
mem_debug[i].ptr = ptr; |
mem_debug[i].ptr = ptr; |
mem_debug[i].line = line; |
mem_debug[i].line = line; |
mem_debug[i].size = size; |
mem_debug[i].size = size; |
return; |
return; |
} |
} |
} |
} |
RDEBUG(1, "WARNING - memdebug out of space!!!!"); |
RDEBUG(1, "WARNING - memdebug out of space!!!!"); |
} | } |
| |
STATIC void fusd_mem_del(void *ptr) | STATIC void fusd_mem_del(void *ptr) |
{ | { |
int i; |
int i; |
for (i = 0; i < MAX_MEM_DEBUG; i++) { |
for (i = 0; i < MAX_MEM_DEBUG; i++) { |
if (mem_debug[i].ptr == ptr) { |
if (mem_debug[i].ptr == ptr) { |
mem_debug[i].ptr = NULL; |
mem_debug[i].ptr = NULL; |
return; |
return; |
} |
} |
} |
} |
RDEBUG(2, "WARNING - memdebug is confused!!!!"); |
RDEBUG(2, "WARNING - memdebug is confused!!!!"); |
} | } |
| |
| |
STATIC void *fusd_kmalloc(size_t size, int type, int line) | STATIC void *fusd_kmalloc(size_t size, int type, int line) |
{ | { |
void *ptr = kmalloc(size, type); |
void *ptr = kmalloc(size, type); |
down(&fusd_memdebug_sem); |
down(&fusd_memdebug_sem); |
fusd_mem_add(ptr, line, size); |
fusd_mem_add(ptr, line, size); |
up(&fusd_memdebug_sem); |
up(&fusd_memdebug_sem); |
return ptr; |
return ptr; |
} | } |
| |
STATIC void fusd_kfree(void *ptr) | STATIC void fusd_kfree(void *ptr) |
{ | { |
down(&fusd_memdebug_sem); |
down(&fusd_memdebug_sem); |
fusd_mem_del(ptr); |
fusd_mem_del(ptr); |
kfree(ptr); |
kfree(ptr); |
up(&fusd_memdebug_sem); |
up(&fusd_memdebug_sem); |
} | } |
| |
STATIC void *fusd_vmalloc(size_t size, int line) | STATIC void *fusd_vmalloc(size_t size, int line) |
{ | { |
void *ptr = vmalloc(size); |
void *ptr = vmalloc(size); |
down(&fusd_memdebug_sem); |
down(&fusd_memdebug_sem); |
fusd_mem_add(ptr, line, size); |
fusd_mem_add(ptr, line, size); |
up(&fusd_memdebug_sem); |
up(&fusd_memdebug_sem); |
return ptr; |
return ptr; |
} | } |
| |
STATIC void fusd_vfree(void *ptr) | STATIC void fusd_vfree(void *ptr) |
{ | { |
down(&fusd_memdebug_sem); |
down(&fusd_memdebug_sem); |
fusd_mem_del(ptr); |
fusd_mem_del(ptr); |
vfree(ptr); |
vfree(ptr); |
up(&fusd_memdebug_sem); |
up(&fusd_memdebug_sem); |
} | } |
| |
#endif /* CONFIG_FUSD_MEMDEBUG */ | #endif /* CONFIG_FUSD_MEMDEBUG */ |
|
|
| |
STATIC inline void init_fusd_msg(fusd_msg_t *fusd_msg) | STATIC inline void init_fusd_msg(fusd_msg_t *fusd_msg) |
{ | { |
if (fusd_msg == NULL) |
if (fusd_msg == NULL) |
return; |
return; |
| |
memset(fusd_msg, 0, sizeof(fusd_msg_t)); |
memset(fusd_msg, 0, sizeof(fusd_msg_t)); |
fusd_msg->magic = FUSD_MSG_MAGIC; |
fusd_msg->magic = FUSD_MSG_MAGIC; |
fusd_msg->cmd = FUSD_FOPS_CALL; /* typical, but can be overwritten */ |
fusd_msg->cmd = FUSD_FOPS_CALL; /* typical, but can be overwritten */ |
} | } |
| |
/* | /* |
|
|
*/ | */ |
STATIC inline void free_fusd_msg(fusd_msg_t **fusd_msg) | STATIC inline void free_fusd_msg(fusd_msg_t **fusd_msg) |
{ | { |
if (fusd_msg == NULL || *fusd_msg == NULL) |
if (fusd_msg == NULL || *fusd_msg == NULL) |
return; |
return; |
| |
if ((*fusd_msg)->data != NULL) { |
if ((*fusd_msg)->data != NULL) { |
VFREE((*fusd_msg)->data); |
VFREE((*fusd_msg)->data); |
(*fusd_msg)->data = NULL; |
(*fusd_msg)->data = NULL; |
} |
} |
KFREE(*fusd_msg); |
KFREE(*fusd_msg); |
*fusd_msg = NULL; |
*fusd_msg = NULL; |
} | } |
| |
| |
/* adjust the size of the 'files' array attached to the device to | /* adjust the size of the 'files' array attached to the device to |
* better match the number of files. In all cases, size must be at |
* better match the number of files. In all cases, size must be at |
* least MIN_ARRAY_SIZE. Subject to that constraint: if |
* least MIN_ARRAY_SIZE. Subject to that constraint: if |
* num_files==array_size, the size is doubled; if | * num_files==array_size, the size is doubled; if |
* num_files<array_size/4, the size is halved. Array is kept as is if |
* num_files<array_size/4, the size is halved. Array is kept as is if |
* the malloc fails. Returns a pointer to the new file struct or NULL |
* the malloc fails. Returns a pointer to the new file struct or NULL |
* if there isn't one. */ | * if there isn't one. */ |
STATIC fusd_file_t **fusd_dev_adjsize(fusd_dev_t *fusd_dev) | STATIC fusd_file_t **fusd_dev_adjsize(fusd_dev_t *fusd_dev) |
{ | { |
fusd_file_t **old_array; |
fusd_file_t **old_array; |
int old_size; |
int old_size; |
| |
old_array = fusd_dev->files; |
old_array = fusd_dev->files; |
old_size = fusd_dev->array_size; |
old_size = fusd_dev->array_size; |
| |
/* compute the new size of the array */ |
/* compute the new size of the array */ |
if (fusd_dev->array_size > 4*fusd_dev->num_files) |
if (fusd_dev->array_size > 4*fusd_dev->num_files) |
fusd_dev->array_size /= 2; |
fusd_dev->array_size /= 2; |
else if (fusd_dev->array_size == fusd_dev->num_files) |
else if (fusd_dev->array_size == fusd_dev->num_files) |
fusd_dev->array_size *= 2; |
fusd_dev->array_size *= 2; |
|
|
/* respect the minimums and maximums (policy) */ |
/* respect the minimums and maximums (policy) */ |
if (fusd_dev->array_size < MIN_FILEARRAY_SIZE) |
if (fusd_dev->array_size < MIN_FILEARRAY_SIZE) |
fusd_dev->array_size = MIN_FILEARRAY_SIZE; |
fusd_dev->array_size = MIN_FILEARRAY_SIZE; |
if (fusd_dev->array_size > MAX_FILEARRAY_SIZE) |
if (fusd_dev->array_size > MAX_FILEARRAY_SIZE) |
fusd_dev->array_size = MAX_FILEARRAY_SIZE; |
fusd_dev->array_size = MAX_FILEARRAY_SIZE; |
|
|
/* make sure it's sane */ |
/* make sure it's sane */ |
if (fusd_dev->array_size < fusd_dev->num_files) { |
if (fusd_dev->array_size < fusd_dev->num_files) { |
RDEBUG(0, "fusd_dev_adjsize is royally screwed up!!!!!"); |
RDEBUG(0, "fusd_dev_adjsize is royally screwed up!!!!!"); |
return fusd_dev->files; |
return fusd_dev->files; |
} |
} |
|
|
/* create a new array. if successful, copy the contents of the old |
/* create a new array. if successful, copy the contents of the old |
* one. if not, revert back to the old. */ |
* one. if not, revert back to the old. */ |
fusd_dev->files = KMALLOC(fusd_dev->array_size * sizeof(fusd_file_t *), |
fusd_dev->files = KMALLOC(fusd_dev->array_size * sizeof(fusd_file_t *), |
GFP_KERNEL); |
GFP_KERNEL); |
if (fusd_dev->files == NULL) { |
if (fusd_dev->files == NULL) { |
RDEBUG(1, "malloc failed in fusd_dev_adjsize!"); |
RDEBUG(1, "malloc failed in fusd_dev_adjsize!"); |
fusd_dev->files = old_array; |
fusd_dev->files = old_array; |
fusd_dev->array_size = old_size; |
fusd_dev->array_size = old_size; |
} else { |
} else { |
RDEBUG(10, "/dev/%s now has space for %d files (had %d)", NAME(fusd_dev), |
RDEBUG(10, "/dev/%s now has space for %d files (had %d)", NAME(fusd_dev), |
fusd_dev->array_size, old_size); |
fusd_dev->array_size, old_size); |
memset(fusd_dev->files, 0, fusd_dev->array_size * sizeof(fusd_file_t *)); |
memset(fusd_dev->files, 0, fusd_dev->array_size * sizeof(fusd_file_t *)); |
memcpy(fusd_dev->files, old_array, |
memcpy(fusd_dev->files, old_array, |
fusd_dev->num_files * sizeof(fusd_file_t *)); |
fusd_dev->num_files * sizeof(fusd_file_t *)); |
KFREE(old_array); |
KFREE(old_array); |
} |
} |
| |
return fusd_dev->files; |
return fusd_dev->files; |
} | } |
| |
| |
/* | /* |
* DEVICE LOCK MUST BE HELD TO CALL THIS FUNCTION | * DEVICE LOCK MUST BE HELD TO CALL THIS FUNCTION |
* |
* |
* This function frees a device IF there is nothing left that is | * This function frees a device IF there is nothing left that is |
* referencing it. | * referencing it. |
* | * |
* Specifically, we do not free the device if: | * Specifically, we do not free the device if: |
* - The driver is still active (i.e. device is not a zombie) |
* - The driver is still active (i.e. device is not a zombie) |
* - There are still files with the device open |
* - There are still files with the device open |
* - There is an open in progress, i.e. a client has verified that |
* - There is an open in progress, i.e. a client has verified that |
* this is a valid device and is getting ready to add itself as an |
* this is a valid device and is getting ready to add itself as an |
* open file. |
* open file. |
* | * |
* If the device is safe to free, it is removed from the valid list | * If the device is safe to free, it is removed from the valid list |
* (in verysafe mode only) and freed. | * (in verysafe mode only) and freed. |
* | * |
* Returns: 1 if the device was freed |
* Returns: 1 if the device was freed |
* 0 if the device still exists (and can be unlocked) */ |
* 0 if the device still exists (and can be unlocked) */ |
STATIC int maybe_free_fusd_dev(fusd_dev_t *fusd_dev) | STATIC int maybe_free_fusd_dev(fusd_dev_t *fusd_dev) |
{ | { |
fusd_msgC_t *ptr, *next; |
fusd_msgC_t *ptr, *next; |
| |
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
| |
/* DON'T free the device under conditions listed above */ |
/* DON'T free the device under conditions listed above */ |
if (!fusd_dev->zombie || fusd_dev->num_files || fusd_dev->open_in_progress) { |
if (!fusd_dev->zombie || fusd_dev->num_files || fusd_dev->open_in_progress) { |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
return 0; |
return 0; |
} |
} |
|
|
/* OK - bombs away! This fusd_dev_t is on its way out the door! */ |
/* OK - bombs away! This fusd_dev_t is on its way out the door! */ |
|
|
RDEBUG(8, "freeing state associated with /dev/%s", NAME(fusd_dev)); |
RDEBUG(8, "freeing state associated with /dev/%s", NAME(fusd_dev)); |
|
|
/* delete it off the list of valid devices, and unlock */ |
/* delete it off the list of valid devices, and unlock */ |
list_del(&fusd_dev->devlist); |
list_del(&fusd_dev->devlist); |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
|
|
/* free any outgoing messages that the device might have waiting */ |
/* free any outgoing messages that the device might have waiting */ |
for (ptr = fusd_dev->msg_head; ptr != NULL; ptr = next) { |
for (ptr = fusd_dev->msg_head; ptr != NULL; ptr = next) { |
next = ptr->next; |
next = ptr->next; |
FREE_FUSD_MSGC(ptr); |
FREE_FUSD_MSGC(ptr); |
} |
} |
|
|
/* free the device's dev name */ |
/* free the device's dev name */ |
if (fusd_dev->dev_name != NULL) { |
if (fusd_dev->dev_name != NULL) { |
KFREE(fusd_dev->dev_name); |
KFREE(fusd_dev->dev_name); |
fusd_dev->dev_name = NULL; |
fusd_dev->dev_name = NULL; |
} |
} |
|
|
/* free the device's class name */ |
/* free the device's class name */ |
if (fusd_dev->class_name != NULL) { |
if (fusd_dev->class_name != NULL) { |
KFREE(fusd_dev->class_name); |
KFREE(fusd_dev->class_name); |
fusd_dev->class_name = NULL; |
fusd_dev->class_name = NULL; |
} |
} |
|
|
/* free the device's name */ |
/* free the device's name */ |
if (fusd_dev->name != NULL) { |
if (fusd_dev->name != NULL) { |
KFREE(fusd_dev->name); |
KFREE(fusd_dev->name); |
fusd_dev->name = NULL; |
fusd_dev->name = NULL; |
} |
} |
|
|
|
|
/* free the array used to store pointers to fusd_file_t's */ |
/* free the array used to store pointers to fusd_file_t's */ |
if (fusd_dev->files != NULL) { |
if (fusd_dev->files != NULL) { |
KFREE(fusd_dev->files); |
KFREE(fusd_dev->files); |
fusd_dev->files = NULL; |
fusd_dev->files = NULL; |
} |
} |
|
|
/* clear the structure and free it! */ |
/* clear the structure and free it! */ |
memset(fusd_dev, 0, sizeof(fusd_dev_t)); |
memset(fusd_dev, 0, sizeof(fusd_dev_t)); |
KFREE(fusd_dev); |
KFREE(fusd_dev); |
|
|
/* notify fusd_status readers that there has been a change in the |
/* notify fusd_status readers that there has been a change in the |
* list of registered devices */ |
* list of registered devices */ |
atomic_inc_and_ret(&last_version); |
atomic_inc_and_ret(&last_version); |
wake_up_interruptible(&new_device_wait); |
wake_up_interruptible(&new_device_wait); |
| |
//MOD_DEC_USE_COUNT; |
//MOD_DEC_USE_COUNT; |
return 1; |
return 1; |
} | } |
| |
| |
|
|
* | * |
* DO NOT CALL THIS FUNCTION UNLESS THE DEVICE IS ALREADY LOCKED | * DO NOT CALL THIS FUNCTION UNLESS THE DEVICE IS ALREADY LOCKED |
* | * |
* zombify_device: called when the driver disappears. Indicates that |
* zombify_device: called when the driver disappears. Indicates that |
* the driver is no longer available to service requests. If there |
* the driver is no longer available to service requests. If there |
* are no outstanding system calls waiting for the fusd_dev state, the | * are no outstanding system calls waiting for the fusd_dev state, the |
* device state itself is freed. | * device state itself is freed. |
* | * |
*/ | */ |
STATIC void zombify_dev(fusd_dev_t *fusd_dev) | STATIC void zombify_dev(fusd_dev_t *fusd_dev) |
{ | { |
int i; |
int i; |
| |
if (fusd_dev->zombie) { |
if (fusd_dev->zombie) { |
RDEBUG(1, "zombify_device called on a zombie!!"); |
RDEBUG(1, "zombify_device called on a zombie!!"); |
return; |
return; |
} |
} |
|
|
fusd_dev->zombie = 1; |
fusd_dev->zombie = 1; |
|
|
RDEBUG(3, "/dev/%s turning into a zombie (%d open files)", NAME(fusd_dev), |
RDEBUG(3, "/dev/%s turning into a zombie (%d open files)", NAME(fusd_dev), |
fusd_dev->num_files); |
fusd_dev->num_files); |
|
|
/* If there are files holding this device open, wake them up. */ |
/* If there are files holding this device open, wake them up. */ |
for (i = 0; i < fusd_dev->num_files; i++) { |
for (i = 0; i < fusd_dev->num_files; i++) { |
wake_up_interruptible(&fusd_dev->files[i]->file_wait); |
wake_up_interruptible(&fusd_dev->files[i]->file_wait); |
wake_up_interruptible(&fusd_dev->files[i]->poll_wait); |
wake_up_interruptible(&fusd_dev->files[i]->poll_wait); |
} |
} |
} | } |
| |
| |
| |
/* utility function to find the index of a fusd_file in a fusd_dev. | /* utility function to find the index of a fusd_file in a fusd_dev. |
* returns index if found, -1 if not found. ASSUMES WE HAVE A VALID |
* returns index if found, -1 if not found. ASSUMES WE HAVE A VALID |
* fusd_dev. fusd_file may be NULL if we are searching for an empty |
* fusd_dev. fusd_file may be NULL if we are searching for an empty |
* slot. */ | * slot. */ |
STATIC int find_fusd_file(fusd_dev_t *fusd_dev, fusd_file_t *fusd_file) | STATIC int find_fusd_file(fusd_dev_t *fusd_dev, fusd_file_t *fusd_file) |
{ | { |
int i, num_files = fusd_dev->num_files; |
int i, num_files = fusd_dev->num_files; |
fusd_file_t **files = fusd_dev->files; |
fusd_file_t **files = fusd_dev->files; |
| |
for (i = 0; i < num_files; i++) |
for (i = 0; i < num_files; i++) |
if (files[i] == fusd_file) |
if (files[i] == fusd_file) |
return i; |
return i; |
| |
return -1; |
return -1; |
} | } |
| |
| |
/* | /* |
* DEVICE LOCK MUST BE HELD BEFORE THIS IS CALLED | * DEVICE LOCK MUST BE HELD BEFORE THIS IS CALLED |
* | * |
* Returns 1 if the device was also freed. 0 if only the file was |
* Returns 1 if the device was also freed. 0 if only the file was |
* freed. If the device is freed, then do not try to unlock it! |
* freed. If the device is freed, then do not try to unlock it! |
* (Callers: Check the return value before unlocking!) | * (Callers: Check the return value before unlocking!) |
*/ | */ |
STATIC int free_fusd_file(fusd_dev_t *fusd_dev, fusd_file_t *fusd_file) | STATIC int free_fusd_file(fusd_dev_t *fusd_dev, fusd_file_t *fusd_file) |
{ | { |
int i; |
int i; |
struct list_head *tmp, *it; |
struct list_head *tmp, *it; |
|
|
/* find the index of the file in the device's file-list... */ |
|
if ((i = find_fusd_file(fusd_dev, fusd_file)) < 0) |
|
panic("corrupted fusd_dev: releasing a file that we think is closed"); |
|
|
|
/* ...and remove it (by putting the last entry into its place) */ |
|
fusd_dev->files[i] = fusd_dev->files[--(fusd_dev->num_files)]; |
|
|
|
/* there might be an incoming message waiting for a restarted system |
|
* call. free it -- after possibly forging a close (see |
|
* fusd_forge_close). */ |
|
|
|
|
|
list_for_each_safe(it, tmp, &fusd_file->transactions) |
|
{ |
|
struct fusd_transaction* transaction = list_entry(it, struct fusd_transaction, list); |
|
if(transaction->msg_in) |
|
{ |
|
if (transaction->msg_in->subcmd == FUSD_OPEN && transaction->msg_in->parm.fops_msg.retval == 0) |
|
fusd_forge_close(transaction->msg_in, fusd_dev); |
|
free_fusd_msg(&transaction->msg_in); |
|
} |
|
KFREE(transaction); |
|
} |
|
|
|
/* free state associated with this file */ |
|
memset(fusd_file, 0, sizeof(fusd_file_t)); |
|
KFREE(fusd_file); |
|
|
|
/* reduce the size of the file array if necessary */ |
|
if (fusd_dev->array_size > MIN_FILEARRAY_SIZE && |
|
fusd_dev->array_size > 4*fusd_dev->num_files) |
|
fusd_dev_adjsize(fusd_dev); |
|
|
|
/* renumber the array */ |
|
for (i = 0; i < fusd_dev->num_files; i++) |
|
fusd_dev->files[i]->index = i; |
|
| |
/* try to free the device -- this may have been its last file */ |
/* find the index of the file in the device's file-list... */ |
return maybe_free_fusd_dev(fusd_dev); |
if ((i = find_fusd_file(fusd_dev, fusd_file)) < 0) |
|
panic("corrupted fusd_dev: releasing a file that we think is closed"); |
|
|
|
/* ...and remove it (by putting the last entry into its place) */ |
|
fusd_dev->files[i] = fusd_dev->files[--(fusd_dev->num_files)]; |
|
|
|
/* there might be an incoming message waiting for a restarted system |
|
* call. free it -- after possibly forging a close (see |
|
* fusd_forge_close). */ |
|
|
|
|
|
list_for_each_safe(it, tmp, &fusd_file->transactions) |
|
{ |
|
struct fusd_transaction* transaction = list_entry(it, struct fusd_transaction, list); |
|
if(transaction->msg_in) |
|
{ |
|
if (transaction->msg_in->subcmd == FUSD_OPEN && transaction->msg_in->parm.fops_msg.retval == 0) |
|
fusd_forge_close(transaction->msg_in, fusd_dev); |
|
free_fusd_msg(&transaction->msg_in); |
|
} |
|
KFREE(transaction); |
|
} |
|
|
|
/* free state associated with this file */ |
|
memset(fusd_file, 0, sizeof(fusd_file_t)); |
|
KFREE(fusd_file); |
|
|
|
/* reduce the size of the file array if necessary */ |
|
if (fusd_dev->array_size > MIN_FILEARRAY_SIZE && |
|
fusd_dev->array_size > 4*fusd_dev->num_files) |
|
fusd_dev_adjsize(fusd_dev); |
|
|
|
/* renumber the array */ |
|
for (i = 0; i < fusd_dev->num_files; i++) |
|
fusd_dev->files[i]->index = i; |
|
|
|
/* try to free the device -- this may have been its last file */ |
|
return maybe_free_fusd_dev(fusd_dev); |
} | } |
| |
| |
|
|
* | * |
* In the even LESS (hopefully very rare) case when one PID had an | * In the even LESS (hopefully very rare) case when one PID had an |
* interrupted syscall, but a different PID is the next to do a system | * interrupted syscall, but a different PID is the next to do a system |
* call on that file descriptor -- well, we lose. Clear state of that |
* call on that file descriptor -- well, we lose. Clear state of that |
* old syscall out and continue as usual. | * old syscall out and continue as usual. |
*/ | */ |
STATIC struct fusd_transaction* fusd_find_incomplete_transaction(fusd_file_t *fusd_file, int subcmd) | STATIC struct fusd_transaction* fusd_find_incomplete_transaction(fusd_file_t *fusd_file, int subcmd) |
{ | { |
struct fusd_transaction* transaction = fusd_find_transaction_by_pid(fusd_file, current->pid); |
struct fusd_transaction* transaction = fusd_find_transaction_by_pid(fusd_file, current->pid); |
if(transaction == NULL) |
if(transaction == NULL) |
return NULL; |
return NULL; |
|
|
|
|
if (transaction->subcmd != subcmd) |
if (transaction->subcmd != subcmd) |
{ |
{ |
RDEBUG(2, "Incomplete transaction %ld thrown out, was expecting subcmd %d but received %d", |
RDEBUG(2, "Incomplete transaction %ld thrown out, was expecting subcmd %d but received %d", |
transaction->transid, transaction->subcmd, subcmd); |
transaction->transid, transaction->subcmd, subcmd); |
fusd_cleanup_transaction(fusd_file, transaction); |
fusd_cleanup_transaction(fusd_file, transaction); |
return NULL; |
return NULL; |
} |
} |
|
|
RDEBUG(4, "pid %d restarting system call with transid %ld", current->pid, |
RDEBUG(4, "pid %d restarting system call with transid %ld", current->pid, |
transaction->transid); |
transaction->transid); |
return transaction; |
return transaction; |
} | } |
| |
| |
STATIC int send_to_dev(fusd_dev_t *fusd_dev, fusd_msg_t *fusd_msg, int locked) | STATIC int send_to_dev(fusd_dev_t *fusd_dev, fusd_msg_t *fusd_msg, int locked) |
{ | { |
fusd_msgC_t *fusd_msgC; |
fusd_msgC_t *fusd_msgC; |
| |
/* allocate a container for the message */ |
/* allocate a container for the message */ |
if ((fusd_msgC = KMALLOC(sizeof(fusd_msgC_t), GFP_KERNEL)) == NULL) |
if ((fusd_msgC = KMALLOC(sizeof(fusd_msgC_t), GFP_KERNEL)) == NULL) |
return -ENOMEM; |
return -ENOMEM; |
| |
memset(fusd_msgC, 0, sizeof(fusd_msgC_t)); |
memset(fusd_msgC, 0, sizeof(fusd_msgC_t)); |
memcpy(&fusd_msgC->fusd_msg, fusd_msg, sizeof(fusd_msg_t)); |
memcpy(&fusd_msgC->fusd_msg, fusd_msg, sizeof(fusd_msg_t)); |
| |
if (!locked) |
if (!locked) |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
| |
/* put the message in the device's outgoing queue. */ |
/* put the message in the device's outgoing queue. */ |
if (fusd_dev->msg_head == NULL) { |
if (fusd_dev->msg_head == NULL) { |
fusd_dev->msg_head = fusd_dev->msg_tail = fusd_msgC; |
fusd_dev->msg_head = fusd_dev->msg_tail = fusd_msgC; |
} else { |
} else { |
fusd_dev->msg_tail->next = fusd_msgC; |
fusd_dev->msg_tail->next = fusd_msgC; |
fusd_dev->msg_tail = fusd_msgC; |
fusd_dev->msg_tail = fusd_msgC; |
} |
} |
| |
if (!locked) |
if (!locked) |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
| |
/* wake up the driver, which now has a message waiting in its queue */ |
/* wake up the driver, which now has a message waiting in its queue */ |
WAKE_UP_INTERRUPTIBLE_SYNC(&fusd_dev->dev_wait); |
WAKE_UP_INTERRUPTIBLE_SYNC(&fusd_dev->dev_wait); |
| |
return 0; |
return 0; |
| |
zombie_dev: | zombie_dev: |
KFREE(fusd_msgC); |
KFREE(fusd_msgC); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
/* |
/* |
* special case: if the driver sent back a successful "open", but | * special case: if the driver sent back a successful "open", but |
* there is no file that is actually open, we forge a "close" so that | * there is no file that is actually open, we forge a "close" so that |
* the driver can maintain balanced open/close pairs. We put calls to |
* the driver can maintain balanced open/close pairs. We put calls to |
* this in fusd_fops_reply, when the reply first comes in; and, | * this in fusd_fops_reply, when the reply first comes in; and, |
* free_fusd_file, when we throw away a reply that had been | * free_fusd_file, when we throw away a reply that had been |
* pending for a restart. | * pending for a restart. |
*/ | */ |
STATIC void fusd_forge_close(fusd_msg_t *msg, fusd_dev_t *fusd_dev) | STATIC void fusd_forge_close(fusd_msg_t *msg, fusd_dev_t *fusd_dev) |
{ | { |
RDEBUG(2, "/dev/%s tried to complete an open for transid %ld, " |
RDEBUG(2, "/dev/%s tried to complete an open for transid %ld, " |
"forging a close", NAME(fusd_dev), msg->parm.fops_msg.transid); |
"forging a close", NAME(fusd_dev), msg->parm.fops_msg.transid); |
msg->cmd = FUSD_FOPS_CALL_DROPREPLY; |
msg->cmd = FUSD_FOPS_CALL_DROPREPLY; |
msg->subcmd = FUSD_CLOSE; |
msg->subcmd = FUSD_CLOSE; |
msg->parm.fops_msg.transid = atomic_inc_and_ret(&last_transid); |
msg->parm.fops_msg.transid = atomic_inc_and_ret(&last_transid); |
send_to_dev(fusd_dev, msg, 1); |
send_to_dev(fusd_dev, msg, 1); |
} | } |
| |
| |
|
|
* function is called, but NOT the lock on the fusd_dev | * function is called, but NOT the lock on the fusd_dev |
*/ | */ |
STATIC int fusd_fops_call_send(fusd_file_t *fusd_file_arg, | STATIC int fusd_fops_call_send(fusd_file_t *fusd_file_arg, |
fusd_msg_t *fusd_msg, struct fusd_transaction** transaction) |
fusd_msg_t *fusd_msg, struct fusd_transaction** transaction) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
|
|
|
/* I check this just in case, shouldn't be necessary. */ |
|
GET_FUSD_FILE_AND_DEV(fusd_file_arg, fusd_file, fusd_dev); |
| |
/* I check this just in case, shouldn't be necessary. */ |
/* make sure message is sane */ |
GET_FUSD_FILE_AND_DEV(fusd_file_arg, fusd_file, fusd_dev); |
if ((fusd_msg->data == NULL) != (fusd_msg->datalen == 0)) { |
|
RDEBUG(2, "fusd_fops_call: data pointer and datalen mismatch"); |
|
return -EINVAL; |
|
} |
|
|
|
/* fill the rest of the structure */ |
|
fusd_msg->parm.fops_msg.pid = current->pid; |
|
fusd_msg->parm.fops_msg.uid = current->uid; |
|
fusd_msg->parm.fops_msg.gid = current->gid; |
|
fusd_msg->parm.fops_msg.flags = fusd_file->file->f_flags; |
|
fusd_msg->parm.fops_msg.offset = fusd_file->file->f_pos; |
|
fusd_msg->parm.fops_msg.device_info = fusd_dev->private_data; |
|
fusd_msg->parm.fops_msg.private_info = fusd_file->private_data; |
|
fusd_msg->parm.fops_msg.fusd_file = fusd_file; |
|
fusd_msg->parm.fops_msg.transid = atomic_inc_and_ret(&last_transid); |
|
|
|
/* set up certain state depending on if we expect a reply */ |
|
switch (fusd_msg->cmd) { |
|
|
|
case FUSD_FOPS_CALL: /* common case */ |
|
fusd_msg->parm.fops_msg.hint = fusd_file->index; |
|
|
|
break; |
|
|
|
case FUSD_FOPS_CALL_DROPREPLY: |
|
/* nothing needed */ |
|
break; |
|
|
|
case FUSD_FOPS_NONBLOCK: |
|
fusd_msg->parm.fops_msg.hint = fusd_file->index; |
|
break; |
|
|
|
default: |
|
RDEBUG(0, "whoa - fusd_fops_call_send got msg with unknown cmd!"); |
|
break; |
|
} |
|
|
|
if(transaction != NULL) |
|
{ |
|
int retval; |
|
retval = fusd_add_transaction(fusd_file, fusd_msg->parm.fops_msg.transid, fusd_msg->subcmd, |
|
fusd_msg->parm.fops_msg.length, transaction); |
|
if(retval < 0) |
|
return retval; |
|
} |
| |
/* make sure message is sane */ |
/* now add the message to the device's outgoing queue! */ |
if ((fusd_msg->data == NULL) != (fusd_msg->datalen == 0)) { |
return send_to_dev(fusd_dev, fusd_msg, 0); |
RDEBUG(2, "fusd_fops_call: data pointer and datalen mismatch"); |
|
return -EINVAL; |
|
} |
|
|
|
/* fill the rest of the structure */ |
|
fusd_msg->parm.fops_msg.pid = current->pid; |
|
fusd_msg->parm.fops_msg.uid = current->uid; |
|
fusd_msg->parm.fops_msg.gid = current->gid; |
|
fusd_msg->parm.fops_msg.flags = fusd_file->file->f_flags; |
|
fusd_msg->parm.fops_msg.offset = fusd_file->file->f_pos; |
|
fusd_msg->parm.fops_msg.device_info = fusd_dev->private_data; |
|
fusd_msg->parm.fops_msg.private_info = fusd_file->private_data; |
|
fusd_msg->parm.fops_msg.fusd_file = fusd_file; |
|
fusd_msg->parm.fops_msg.transid = atomic_inc_and_ret(&last_transid); |
|
|
|
/* set up certain state depending on if we expect a reply */ |
|
switch (fusd_msg->cmd) { |
|
|
|
case FUSD_FOPS_CALL: /* common case */ |
|
fusd_msg->parm.fops_msg.hint = fusd_file->index; |
|
|
|
break; |
|
|
|
case FUSD_FOPS_CALL_DROPREPLY: |
|
/* nothing needed */ |
|
break; |
|
|
|
case FUSD_FOPS_NONBLOCK: |
|
fusd_msg->parm.fops_msg.hint = fusd_file->index; |
|
break; |
|
|
|
default: |
|
RDEBUG(0, "whoa - fusd_fops_call_send got msg with unknown cmd!"); |
|
break; |
|
} |
|
|
|
if(transaction != NULL) |
|
{ |
|
int retval; |
|
retval = fusd_add_transaction(fusd_file, fusd_msg->parm.fops_msg.transid, fusd_msg->subcmd, |
|
fusd_msg->parm.fops_msg.length, transaction); |
|
if(retval < 0) |
|
return retval; |
|
} |
|
|
|
/* now add the message to the device's outgoing queue! */ |
|
return send_to_dev(fusd_dev, fusd_msg, 0); |
|
| |
| |
/* bizarre errors go straight here */ |
/* bizarre errors go straight here */ |
invalid_dev: | invalid_dev: |
invalid_file: | invalid_file: |
RDEBUG(0, "fusd_fops_call: got invalid device or file!!!!"); |
RDEBUG(0, "fusd_fops_call: got invalid device or file!!!!"); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
|
|
* function is called, but NOT the lock on the fusd_dev | * function is called, but NOT the lock on the fusd_dev |
*/ | */ |
STATIC int fusd_fops_call_wait(fusd_file_t *fusd_file_arg, | STATIC int fusd_fops_call_wait(fusd_file_t *fusd_file_arg, |
fusd_msg_t **fusd_msg_reply, struct fusd_transaction* transaction) |
fusd_msg_t **fusd_msg_reply, struct fusd_transaction* transaction) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
int retval; |
int retval; |
|
|
/* I check this just in case, shouldn't be necessary. */ |
/* I check this just in case, shouldn't be necessary. */ |
GET_FUSD_FILE_AND_DEV(fusd_file_arg, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(fusd_file_arg, fusd_file, fusd_dev); |
|
|
/* initialize first to tell callers there is no reply (yet) */ |
/* initialize first to tell callers there is no reply (yet) */ |
if (fusd_msg_reply != NULL) |
if (fusd_msg_reply != NULL) |
*fusd_msg_reply = NULL; |
*fusd_msg_reply = NULL; |
|
|
/* |
/* |
* Now, lock the device, check for an incoming message, and sleep if |
* Now, lock the device, check for an incoming message, and sleep if |
* there is not a message already waiting for us. Note that we are |
* there is not a message already waiting for us. Note that we are |
* unrolling the interruptible_sleep_on, as in the kernel's |
* unrolling the interruptible_sleep_on, as in the kernel's |
* fs/pipe.c, to avoid race conditions between checking for the |
* fs/pipe.c, to avoid race conditions between checking for the |
* sleep condition and sleeping. |
* sleep condition and sleeping. |
*/ |
*/ |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
while (transaction->msg_in == NULL) { |
while (transaction->msg_in == NULL) { |
DECLARE_WAITQUEUE(wait, current); |
DECLARE_WAITQUEUE(wait, current); |
|
|
RDEBUG(10, "pid %d blocking on transid %ld", current->pid, transaction->transid); |
RDEBUG(10, "pid %d blocking on transid %ld", current->pid, transaction->transid); |
current->state = TASK_INTERRUPTIBLE; |
current->state = TASK_INTERRUPTIBLE; |
add_wait_queue(&fusd_file->file_wait, &wait); |
add_wait_queue(&fusd_file->file_wait, &wait); |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
|
|
schedule(); |
schedule(); |
remove_wait_queue(&fusd_file->file_wait, &wait); |
remove_wait_queue(&fusd_file->file_wait, &wait); |
current->state = TASK_RUNNING; |
current->state = TASK_RUNNING; |
|
|
/* |
/* |
* If we woke up due to a signal -- and not due to a reply message |
* If we woke up due to a signal -- and not due to a reply message |
* coming in -- then we are in some trouble. The driver is already |
* coming in -- then we are in some trouble. The driver is already |
* processing the request and might have changed some state that is |
* processing the request and might have changed some state that is |
* hard to roll back. So, we'll tell the process to restart the |
* hard to roll back. So, we'll tell the process to restart the |
* system call, and come back to this point when the system call is |
* system call, and come back to this point when the system call is |
* restarted. We need to remember the PID to avoid confusion in |
* restarted. We need to remember the PID to avoid confusion in |
* case there is another process holding this file descriptor that |
* case there is another process holding this file descriptor that |
* is also trying to make a call. |
* is also trying to make a call. |
*/ |
*/ |
if (signal_pending(current)) { |
if (signal_pending(current)) { |
RDEBUG(5, "blocked pid %d got a signal; sending -ERESTARTSYS", |
RDEBUG(5, "blocked pid %d got a signal; sending -ERESTARTSYS", |
current->pid); |
current->pid); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
return -ERESTARTSYS; |
return -ERESTARTSYS; |
} |
} |
|
|
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
/* re-lock the device, so we can do our msg_in check again */ |
/* re-lock the device, so we can do our msg_in check again */ |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
} |
} |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
|
|
/* ok - at this point we are awake due to a message received. */ |
/* ok - at this point we are awake due to a message received. */ |
|
|
if (transaction->msg_in->cmd != FUSD_FOPS_REPLY || |
if (transaction->msg_in->cmd != FUSD_FOPS_REPLY || |
transaction->msg_in->subcmd != transaction->subcmd || |
transaction->msg_in->subcmd != transaction->subcmd || |
transaction->msg_in->parm.fops_msg.transid != transaction->transid || |
transaction->msg_in->parm.fops_msg.transid != transaction->transid || |
transaction->msg_in->parm.fops_msg.fusd_file != fusd_file) { |
transaction->msg_in->parm.fops_msg.fusd_file != fusd_file) { |
RDEBUG(2, "fusd_fops_call: invalid reply!"); |
RDEBUG(2, "fusd_fops_call: invalid reply!"); |
goto invalid_reply; |
goto invalid_reply; |
} |
} |
|
|
/* copy metadata back from userspace */ |
/* copy metadata back from userspace */ |
fusd_file->file->f_flags = transaction->msg_in->parm.fops_msg.flags; |
fusd_file->file->f_flags = transaction->msg_in->parm.fops_msg.flags; |
fusd_file->private_data = transaction->msg_in->parm.fops_msg.private_info; |
fusd_file->private_data = transaction->msg_in->parm.fops_msg.private_info; |
/* note, changes to device_info are NO LONGER honored here */ |
/* note, changes to device_info are NO LONGER honored here */ |
|
|
/* if everything's okay, return the return value. if caller is |
/* if everything's okay, return the return value. if caller is |
* willing to take responsibility for freeing the message itself, we |
* willing to take responsibility for freeing the message itself, we |
* return the message too. */ |
* return the message too. */ |
retval = transaction->msg_in->parm.fops_msg.retval; |
retval = transaction->msg_in->parm.fops_msg.retval; |
if (fusd_msg_reply != NULL) { |
if (fusd_msg_reply != NULL) { |
/* NOW TRANSFERRING RESPONSIBILITY FOR FREEING THIS DATA TO THE CALLER */ |
/* NOW TRANSFERRING RESPONSIBILITY FOR FREEING THIS DATA TO THE CALLER */ |
*fusd_msg_reply = transaction->msg_in; |
*fusd_msg_reply = transaction->msg_in; |
transaction->msg_in = NULL; |
transaction->msg_in = NULL; |
} else { |
} else { |
/* free the message ourselves */ |
/* free the message ourselves */ |
free_fusd_msg(&transaction->msg_in); |
free_fusd_msg(&transaction->msg_in); |
} |
} |
|
|
/* success */ |
/* success */ |
fusd_cleanup_transaction(fusd_file, transaction); |
fusd_cleanup_transaction(fusd_file, transaction); |
return retval; |
return retval; |
| |
invalid_reply: | invalid_reply: |
fusd_cleanup_transaction(fusd_file, transaction); |
fusd_cleanup_transaction(fusd_file, transaction); |
return -EPIPE; |
return -EPIPE; |
| |
/* bizarre errors go straight here */ |
/* bizarre errors go straight here */ |
invalid_dev: | invalid_dev: |
invalid_file: | invalid_file: |
RDEBUG(0, "fusd_fops_call: got invalid device or file!!!!"); |
RDEBUG(0, "fusd_fops_call: got invalid device or file!!!!"); |
return -EPIPE; |
return -EPIPE; |
| |
zombie_dev: | zombie_dev: |
RDEBUG(2, "fusd_fops_call: %s zombified while waiting for reply", |
RDEBUG(2, "fusd_fops_call: %s zombified while waiting for reply", |
NAME(fusd_dev)); |
NAME(fusd_dev)); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
|
|
* fops_call, to destroy the message that was returned to them. */ | * fops_call, to destroy the message that was returned to them. */ |
STATIC void fusd_transaction_done(struct fusd_transaction *transaction) | STATIC void fusd_transaction_done(struct fusd_transaction *transaction) |
{ | { |
transaction->transid = -1; |
transaction->transid = -1; |
transaction->pid = 0; |
transaction->pid = 0; |
} | } |
| |
| |
|
|
/* | /* |
* The process of having a client open a FUSD device is surprisingly | * The process of having a client open a FUSD device is surprisingly |
* tricky -- perhaps the most complex piece of FUSD (or, a close | * tricky -- perhaps the most complex piece of FUSD (or, a close |
* second to poll_diffs). Race conditions are rampant here. |
* second to poll_diffs). Race conditions are rampant here. |
* | * |
* The main problem is that there is a race between clients trying to | * The main problem is that there is a race between clients trying to |
* open the FUSD device, and providers unregistering it (e.g., the | * open the FUSD device, and providers unregistering it (e.g., the |
* driver dying). If the device-unregister callback starts, and is |
* driver dying). If the device-unregister callback starts, and is |
* scheduled out after it locks the fusd device but before it | * scheduled out after it locks the fusd device but before it |
* unregisters the device with devfs, the open callback might be | * unregisters the device with devfs, the open callback might be |
* invoked in this interval. This means the client will down() on a |
* invoked in this interval. This means the client will down() on a |
* semaphore that is about to be freed when the device is destroyed. | * semaphore that is about to be freed when the device is destroyed. |
* | * |
* The only way to fix this, as far as I can tell, is for device | * The only way to fix this, as far as I can tell, is for device |
* registration and unregistration to both share a global lock; the | * registration and unregistration to both share a global lock; the |
* client checks its 'private_data' pointer to make sure it's on the | * client checks its 'private_data' pointer to make sure it's on the |
* list of valid devices. If so, it sets a flag (open_in_progress) |
* list of valid devices. If so, it sets a flag (open_in_progress) |
* which means "Don't free this device yet!". Then, it releases the |
* which means "Don't free this device yet!". Then, it releases the |
* global lock, grabs the device lock, and tries to add itself as a | * global lock, grabs the device lock, and tries to add itself as a |
* "file" to the device array. It is then safe to decrement |
* "file" to the device array. It is then safe to decrement |
* open_in_progress, because being a member of the file array will | * open_in_progress, because being a member of the file array will |
* guarantee that the device will zombify instead of being freed. | * guarantee that the device will zombify instead of being freed. |
* | * |
* Another gotcha: To avoid infinitely dining with philosophers, the | * Another gotcha: To avoid infinitely dining with philosophers, the |
* global lock (fusd_devlist_sem) should always be acquired AFTER a | * global lock (fusd_devlist_sem) should always be acquired AFTER a |
* fusd device is locked. The code path that frees devices acquires |
* fusd device is locked. The code path that frees devices acquires |
* the device lock FIRST, so the code here must do the same. | * the device lock FIRST, so the code here must do the same. |
* | * |
* Because of the complexity of opening a file, I've broken it up into | * Because of the complexity of opening a file, I've broken it up into |
|
|
*/ | */ |
int fusd_dev_is_valid(fusd_dev_t *fusd_dev) | int fusd_dev_is_valid(fusd_dev_t *fusd_dev) |
{ | { |
struct list_head *tmp; |
struct list_head *tmp; |
int dev_found = 0; |
int dev_found = 0; |
| |
/* The first thing we must do is acquire the global lock on the |
/* The first thing we must do is acquire the global lock on the |
* device list, and make sure this device is valid; if so, mark it |
* device list, and make sure this device is valid; if so, mark it |
* as being "in use". If we don't do this, there's a race: after we |
* as being "in use". If we don't do this, there's a race: after we |
* enter this function, the device may be unregistered. */ |
* enter this function, the device may be unregistered. */ |
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
list_for_each(tmp, &fusd_devlist_head) { |
list_for_each(tmp, &fusd_devlist_head) { |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
|
|
if (d == fusd_dev && d->magic == FUSD_DEV_MAGIC && !ZOMBIE(d)) { |
if (d == fusd_dev && d->magic == FUSD_DEV_MAGIC && !ZOMBIE(d)) { |
dev_found = 1; |
dev_found = 1; |
break; |
break; |
} |
} |
} |
} |
|
|
/* A device will not be deallocated when this counter is >0 */ |
/* A device will not be deallocated when this counter is >0 */ |
if (dev_found) |
if (dev_found) |
fusd_dev->open_in_progress++; |
fusd_dev->open_in_progress++; |
| |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
| |
return dev_found; |
return dev_found; |
} | } |
| |
| |
int fusd_dev_add_file(struct file *file, fusd_dev_t *fusd_dev, fusd_file_t **fusd_file_ret) | int fusd_dev_add_file(struct file *file, fusd_dev_t *fusd_dev, fusd_file_t **fusd_file_ret) |
{ | { |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
int i; |
int i; |
| |
/* Make sure the device didn't become a zombie while we were waiting |
/* Make sure the device didn't become a zombie while we were waiting |
* for the device lock */ |
* for the device lock */ |
if (ZOMBIE(fusd_dev)) |
if (ZOMBIE(fusd_dev)) |
return -ENOENT; |
return -ENOENT; |
|
|
/* this shouldn't happen. maybe i'm insane, but i check anyway. */ |
/* this shouldn't happen. maybe i'm insane, but i check anyway. */ |
for (i = 0; i < fusd_dev->num_files; i++) |
for (i = 0; i < fusd_dev->num_files; i++) |
if (fusd_dev->files[i]->file == file) { |
if (fusd_dev->files[i]->file == file) { |
RDEBUG(1, "warning: fusd_client_open got open for already-open file!?"); |
RDEBUG(1, "warning: fusd_client_open got open for already-open file!?"); |
return -EIO; |
return -EIO; |
} |
} |
|
|
/* You can't open your own file! Return -EDEADLOCK if someone tries to. |
/* You can't open your own file! Return -EDEADLOCK if someone tries to. |
* |
* |
* XXX - TODO - FIXME - This should eventually be more general |
* XXX - TODO - FIXME - This should eventually be more general |
* deadlock detection of arbitrary length cycles */ |
* deadlock detection of arbitrary length cycles */ |
if (current->pid == fusd_dev->pid) { |
if (current->pid == fusd_dev->pid) { |
RDEBUG(3, "pid %d tried to open its own device (/dev/%s)", |
RDEBUG(3, "pid %d tried to open its own device (/dev/%s)", |
fusd_dev->pid, NAME(fusd_dev)); |
fusd_dev->pid, NAME(fusd_dev)); |
return -EDEADLOCK; |
return -EDEADLOCK; |
} |
} |
|
|
/* make more space in the file array if we need it */ |
/* make more space in the file array if we need it */ |
if (fusd_dev->num_files == fusd_dev->array_size && |
if (fusd_dev->num_files == fusd_dev->array_size && |
fusd_dev->array_size < MAX_FILEARRAY_SIZE) |
fusd_dev->array_size < MAX_FILEARRAY_SIZE) |
fusd_dev_adjsize(fusd_dev); |
fusd_dev_adjsize(fusd_dev); |
|
|
/* make sure we have room... adjsize may have failed */ |
/* make sure we have room... adjsize may have failed */ |
if (fusd_dev->num_files >= fusd_dev->array_size) { |
if (fusd_dev->num_files >= fusd_dev->array_size) { |
RDEBUG(1, "/dev/%s out of state space for open files!", NAME(fusd_dev)); |
RDEBUG(1, "/dev/%s out of state space for open files!", NAME(fusd_dev)); |
return -ENOMEM; |
return -ENOMEM; |
} |
} |
|
|
/* create state for this file */ |
/* create state for this file */ |
if ((fusd_file = KMALLOC(sizeof(fusd_file_t), GFP_KERNEL)) == NULL) { |
if ((fusd_file = KMALLOC(sizeof(fusd_file_t), GFP_KERNEL)) == NULL) { |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
return -ENOMEM; |
return -ENOMEM; |
} |
} |
memset(fusd_file, 0, sizeof(fusd_file_t)); |
memset(fusd_file, 0, sizeof(fusd_file_t)); |
init_waitqueue_head(&fusd_file->file_wait); |
init_waitqueue_head(&fusd_file->file_wait); |
init_waitqueue_head(&fusd_file->poll_wait); |
init_waitqueue_head(&fusd_file->poll_wait); |
INIT_LIST_HEAD(&fusd_file->transactions); |
INIT_LIST_HEAD(&fusd_file->transactions); |
init_MUTEX(&fusd_file->file_sem); |
init_MUTEX(&fusd_file->file_sem); |
init_MUTEX(&fusd_file->transactions_sem); |
init_MUTEX(&fusd_file->transactions_sem); |
fusd_file->last_poll_sent = -1; |
fusd_file->last_poll_sent = -1; |
fusd_file->magic = FUSD_FILE_MAGIC; |
fusd_file->magic = FUSD_FILE_MAGIC; |
fusd_file->fusd_dev = fusd_dev; |
fusd_file->fusd_dev = fusd_dev; |
fusd_file->fusd_dev_version = fusd_dev->version; |
fusd_file->fusd_dev_version = fusd_dev->version; |
fusd_file->file = file; |
fusd_file->file = file; |
|
|
/* add this file to the list of files managed by the device */ |
/* add this file to the list of files managed by the device */ |
fusd_file->index = fusd_dev->num_files++; |
fusd_file->index = fusd_dev->num_files++; |
fusd_dev->files[fusd_file->index] = fusd_file; |
fusd_dev->files[fusd_file->index] = fusd_file; |
|
|
/* store the pointer to this file with the kernel */ |
/* store the pointer to this file with the kernel */ |
file->private_data = fusd_file; |
file->private_data = fusd_file; |
*fusd_file_ret = fusd_file; |
*fusd_file_ret = fusd_file; |
| |
/* success! */ |
/* success! */ |
return 0; |
return 0; |
} | } |
| |
STATIC struct fusd_dev_t_s* find_user_device(int dev_id) | STATIC struct fusd_dev_t_s* find_user_device(int dev_id) |
{ | { |
struct list_head* entry; |
struct list_head* entry; |
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
list_for_each(entry, &fusd_devlist_head) |
list_for_each(entry, &fusd_devlist_head) |
{ |
{ |
fusd_dev_t *d = list_entry(entry, fusd_dev_t, devlist); |
fusd_dev_t *d = list_entry(entry, fusd_dev_t, devlist); |
if(d->dev_id == dev_id) |
if(d->dev_id == dev_id) |
{ |
{ |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
return d; |
return d; |
} |
} |
} |
} |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
return NULL; | return NULL; |
} | } |
| |
|
|
*/ | */ |
STATIC int fusd_client_open(struct inode *inode, struct file *file) | STATIC int fusd_client_open(struct inode *inode, struct file *file) |
{ | { |
int retval; |
int retval; |
int device_freed = 0; |
int device_freed = 0; |
fusd_dev_t *fusd_dev = find_user_device(inode->i_rdev); |
fusd_dev_t *fusd_dev = find_user_device(inode->i_rdev); |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
fusd_msg_t fusd_msg; |
fusd_msg_t fusd_msg; |
struct fusd_transaction* transaction; |
struct fusd_transaction* transaction; |
|
|
/* If the device wasn't on our valid list, stop here. */ |
/* If the device wasn't on our valid list, stop here. */ |
if (!fusd_dev_is_valid(fusd_dev)) |
if (!fusd_dev_is_valid(fusd_dev)) |
return -ENOENT; |
return -ENOENT; |
|
|
/* fusd_dev->open_in_progress now set */ |
/* fusd_dev->open_in_progress now set */ |
|
|
/* Lock the fusd device. Note, when we finally do acquire the lock, |
/* Lock the fusd device. Note, when we finally do acquire the lock, |
* the device might be a zombie (driver disappeared). */ |
* the device might be a zombie (driver disappeared). */ |
RAWLOCK_FUSD_DEV(fusd_dev); |
RAWLOCK_FUSD_DEV(fusd_dev); |
|
|
RDEBUG(3, "got an open for /dev/%s (owned by pid %d) from pid %d", |
RDEBUG(3, "got an open for /dev/%s (owned by pid %d) from pid %d", |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
/* Try to add ourselves to the device's file list. If retval==0, we |
/* Try to add ourselves to the device's file list. If retval==0, we |
are now part of the file array. */ |
are now part of the file array. */ |
retval = fusd_dev_add_file(file, fusd_dev, &fusd_file); |
retval = fusd_dev_add_file(file, fusd_dev, &fusd_file); |
|
|
/* |
/* |
* It is now safe to unset the open_in_progress flag. Either: |
* It is now safe to unset the open_in_progress flag. Either: |
* 1) We are part of the file array, so dev won't be freed, or; |
* 1) We are part of the file array, so dev won't be freed, or; |
* 2) Something failed, so we are returning a failure now and no |
* 2) Something failed, so we are returning a failure now and no |
* longer need the device. |
* longer need the device. |
* Note, open_in_progress must be protected by the global sem, not |
* Note, open_in_progress must be protected by the global sem, not |
* the device lock, due to the access of it in fusd_dev_is_valid(). |
* the device lock, due to the access of it in fusd_dev_is_valid(). |
*/ |
*/ |
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
fusd_dev->open_in_progress--; |
fusd_dev->open_in_progress--; |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
|
|
/* If adding ourselves to the device list failed, give up. Possibly |
/* If adding ourselves to the device list failed, give up. Possibly |
* free the device if it was a zombie and waiting for us to complete |
* free the device if it was a zombie and waiting for us to complete |
* our open. */ |
* our open. */ |
if (retval < 0) { |
if (retval < 0) { |
if (!maybe_free_fusd_dev(fusd_dev)) |
if (!maybe_free_fusd_dev(fusd_dev)) |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
return retval; |
return retval; |
} |
} |
|
|
/* send message to userspace and get retval */ |
/* send message to userspace and get retval */ |
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
fusd_msg.subcmd = FUSD_OPEN; |
fusd_msg.subcmd = FUSD_OPEN; |
|
|
/* send message to userspace and get the reply. Device can't be |
/* send message to userspace and get the reply. Device can't be |
* locked during that operation. */ |
* locked during that operation. */ |
|
|
UNLOCK_FUSD_DEV(fusd_dev); |
|
retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction); |
|
|
|
if (retval >= 0) |
|
retval = fusd_fops_call_wait(fusd_file, NULL, transaction); |
|
RAWLOCK_FUSD_DEV(fusd_dev); |
|
|
|
/* If the device zombified (while we were waiting to reacquire the |
|
* lock)... consider that a failure */ |
|
if (ZOMBIE(fusd_dev)) |
|
retval = -ENOENT; |
|
|
|
/* if retval is negative, throw away state... the file open failed */ |
|
if (retval < 0) { |
|
RDEBUG(3, "...open failed for /dev/%s (owned by pid %d) from pid %d", |
|
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
|
device_freed = free_fusd_file(fusd_dev, fusd_file); |
|
} |
|
|
|
/* Now unlock the device, if it still exists. (It may have been |
|
* freed if the open failed, and we were the last outstanding |
|
* request for it.) */ |
|
if (!device_freed) |
|
UNLOCK_FUSD_DEV(fusd_dev); |
|
| |
return retval; |
UNLOCK_FUSD_DEV(fusd_dev); |
|
retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction); |
|
|
|
if (retval >= 0) |
|
retval = fusd_fops_call_wait(fusd_file, NULL, transaction); |
|
RAWLOCK_FUSD_DEV(fusd_dev); |
|
|
|
/* If the device zombified (while we were waiting to reacquire the |
|
* lock)... consider that a failure */ |
|
if (ZOMBIE(fusd_dev)) |
|
retval = -ENOENT; |
|
|
|
/* if retval is negative, throw away state... the file open failed */ |
|
if (retval < 0) { |
|
RDEBUG(3, "...open failed for /dev/%s (owned by pid %d) from pid %d", |
|
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
|
device_freed = free_fusd_file(fusd_dev, fusd_file); |
|
} |
|
|
|
/* Now unlock the device, if it still exists. (It may have been |
|
* freed if the open failed, and we were the last outstanding |
|
* request for it.) */ |
|
if (!device_freed) |
|
UNLOCK_FUSD_DEV(fusd_dev); |
|
|
|
return retval; |
} | } |
| |
| |
/* close() has been called on a registered device. like |
/* close() has been called on a registered device. like |
* fusd_client_open, we must lock the entire device. */ | * fusd_client_open, we must lock the entire device. */ |
STATIC int fusd_client_release(struct inode *inode, struct file *file) | STATIC int fusd_client_release(struct inode *inode, struct file *file) |
{ | { |
int retval; |
int retval; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_msg_t fusd_msg; |
fusd_msg_t fusd_msg; |
struct fusd_transaction* transaction; |
struct fusd_transaction* transaction; |
|
|
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
|
|
RDEBUG(3, "got a close on /dev/%s (owned by pid %d) from pid %d", |
RDEBUG(3, "got a close on /dev/%s (owned by pid %d) from pid %d", |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
/* Tell the driver that the file closed, if it still exists. */ |
/* Tell the driver that the file closed, if it still exists. */ |
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
fusd_msg.subcmd = FUSD_CLOSE; |
fusd_msg.subcmd = FUSD_CLOSE; |
retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction); |
retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction); |
RDEBUG(5, "fusd_client_release: send returned %d", retval); |
RDEBUG(5, "fusd_client_release: send returned %d", retval); |
if (retval >= 0) |
if (retval >= 0) |
retval = fusd_fops_call_wait(fusd_file, NULL, transaction); |
retval = fusd_fops_call_wait(fusd_file, NULL, transaction); |
|
|
RDEBUG(5, "fusd_client_release: call_wait %d", retval); |
RDEBUG(5, "fusd_client_release: call_wait %d", retval); |
/* delete the file off the device's file-list, and free it. note |
/* delete the file off the device's file-list, and free it. note |
* that device may be a zombie right now and may be freed when we |
* that device may be a zombie right now and may be freed when we |
* come back from free_fusd_file. we only release the lock if the |
* come back from free_fusd_file. we only release the lock if the |
* device still exists. */ |
* device still exists. */ |
RAWLOCK_FUSD_DEV(fusd_dev); |
RAWLOCK_FUSD_DEV(fusd_dev); |
if (!free_fusd_file(fusd_dev, fusd_file)) { |
if (!free_fusd_file(fusd_dev, fusd_file)) { |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
} |
} |
| |
return retval; |
return retval; |
| |
invalid_dev: | invalid_dev: |
invalid_file: | invalid_file: |
RDEBUG(1, "got a close on client file from pid %d, INVALID DEVICE!", |
RDEBUG(1, "got a close on client file from pid %d, INVALID DEVICE!", |
current->pid); |
current->pid); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
| |
STATIC ssize_t fusd_client_read(struct file *file , char *buf, | STATIC ssize_t fusd_client_read(struct file *file , char *buf, |
size_t count, loff_t *offset) |
size_t count, loff_t *offset) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
struct fusd_transaction* transaction; |
struct fusd_transaction* transaction; |
fusd_msg_t fusd_msg, *reply = NULL; |
fusd_msg_t fusd_msg, *reply = NULL; |
int retval = -EPIPE; |
int retval = -EPIPE; |
|
|
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
|
|
RDEBUG(3, "got a read on /dev/%s (owned by pid %d) from pid %d", |
RDEBUG(3, "got a read on /dev/%s (owned by pid %d) from pid %d", |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_READ); |
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_READ); |
if (transaction && transaction->size > count) |
if (transaction && transaction->size > count) |
{ |
{ |
RDEBUG(3, "Incomplete I/O transaction %ld thrown out, as the transaction's size of %d bytes was greater than " |
RDEBUG(3, "Incomplete I/O transaction %ld thrown out, as the transaction's size of %d bytes was greater than " |
"the retry's size of %d bytes", transaction->transid, transaction->size, (int)count); |
"the retry's size of %d bytes", transaction->transid, transaction->size, (int)count); |
|
|
fusd_cleanup_transaction(fusd_file, transaction); |
fusd_cleanup_transaction(fusd_file, transaction); |
transaction = NULL; |
transaction = NULL; |
} |
} |
|
|
if(transaction == NULL) |
if(transaction == NULL) |
{ |
{ |
/* make sure we aren't trying to read too big of a buffer */ |
/* make sure we aren't trying to read too big of a buffer */ |
if (count > MAX_RW_SIZE) |
if (count > MAX_RW_SIZE) |
count = MAX_RW_SIZE; |
count = MAX_RW_SIZE; |
|
|
/* send the message */ |
/* send the message */ |
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
fusd_msg.subcmd = FUSD_READ; |
fusd_msg.subcmd = FUSD_READ; |
fusd_msg.parm.fops_msg.length = count; |
fusd_msg.parm.fops_msg.length = count; |
|
|
/* send message to userspace */ |
/* send message to userspace */ |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
goto done; |
goto done; |
} |
} |
|
|
/* and wait for the reply */ |
/* and wait for the reply */ |
/* todo: store and retrieve the transid from the interrupted messsage */ |
/* todo: store and retrieve the transid from the interrupted messsage */ |
retval = fusd_fops_call_wait(fusd_file, &reply, transaction); |
retval = fusd_fops_call_wait(fusd_file, &reply, transaction); |
|
|
/* return immediately in case of error */ |
/* return immediately in case of error */ |
if (retval < 0 || reply == NULL) |
if (retval < 0 || reply == NULL) |
goto done; |
goto done; |
|
|
/* adjust the reval if the retval indicates a larger read than the |
/* adjust the reval if the retval indicates a larger read than the |
* data that was actually provided */ |
* data that was actually provided */ |
if (reply->datalen != retval) { |
if (reply->datalen != retval) { |
RDEBUG(1, "warning: /dev/%s driver (pid %d) claimed it returned %d bytes " |
RDEBUG(1, "warning: /dev/%s driver (pid %d) claimed it returned %d bytes " |
"on read but actually returned %d", |
"on read but actually returned %d", |
NAME(fusd_dev), fusd_dev->pid, retval, reply->datalen); |
NAME(fusd_dev), fusd_dev->pid, retval, reply->datalen); |
retval = reply->datalen; |
retval = reply->datalen; |
} |
} |
|
|
/* adjust if the device driver gave us more data than the user asked for |
/* adjust if the device driver gave us more data than the user asked for |
* (bad! bad! why is the driver broken???) */ |
* (bad! bad! why is the driver broken???) */ |
if (retval > count) { |
if (retval > count) { |
RDEBUG(1, "warning: /dev/%s driver (pid %d) returned %d bytes on read but " |
RDEBUG(1, "warning: /dev/%s driver (pid %d) returned %d bytes on read but " |
"the user only asked for %d", |
"the user only asked for %d", |
NAME(fusd_dev), fusd_dev->pid, retval, (int) count); |
NAME(fusd_dev), fusd_dev->pid, retval, (int) count); |
retval = count; |
retval = count; |
} |
} |
|
|
/* copy the offset back from the message */ |
/* copy the offset back from the message */ |
*offset = reply->parm.fops_msg.offset; |
*offset = reply->parm.fops_msg.offset; |
|
|
/* IFF return value indicates data present, copy it back */ |
/* IFF return value indicates data present, copy it back */ |
if (retval > 0) { |
if (retval > 0) { |
if (copy_to_user(buf, reply->data, retval)) { |
if (copy_to_user(buf, reply->data, retval)) { |
retval = -EFAULT; |
retval = -EFAULT; |
goto done; |
goto done; |
} |
} |
} |
} |
| |
done: | done: |
/* clear the readable bit of our cached poll state */ |
/* clear the readable bit of our cached poll state */ |
fusd_file->cached_poll_state &= ~(FUSD_NOTIFY_INPUT); |
fusd_file->cached_poll_state &= ~(FUSD_NOTIFY_INPUT); |
| |
free_fusd_msg(&reply); |
free_fusd_msg(&reply); |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
return retval; |
return retval; |
| |
invalid_file: | invalid_file: |
invalid_dev: | invalid_dev: |
RDEBUG(3, "got a read on client file from pid %d, driver has disappeared", |
RDEBUG(3, "got a read on client file from pid %d, driver has disappeared", |
current->pid); |
current->pid); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
STATIC int fusd_add_transaction(fusd_file_t *fusd_file, int transid, int subcmd, int size, struct fusd_transaction** out_transaction) | STATIC int fusd_add_transaction(fusd_file_t *fusd_file, int transid, int subcmd, int size, struct fusd_transaction** out_transaction) |
{ | { |
struct fusd_transaction* transaction = (struct fusd_transaction*) KMALLOC(sizeof(struct fusd_transaction), GFP_KERNEL); |
struct fusd_transaction* transaction = (struct fusd_transaction*) KMALLOC(sizeof(struct fusd_transaction), GFP_KERNEL); |
if(transaction == NULL) |
if(transaction == NULL) |
return -ENOMEM; |
return -ENOMEM; |
|
|
transaction->msg_in = NULL; |
transaction->msg_in = NULL; |
transaction->transid = transid; |
transaction->transid = transid; |
transaction->subcmd = subcmd; |
transaction->subcmd = subcmd; |
transaction->pid = current->pid; |
transaction->pid = current->pid; |
transaction->size = size; |
transaction->size = size; |
|
|
down(&fusd_file->transactions_sem); |
down(&fusd_file->transactions_sem); |
list_add_tail(&transaction->list, &fusd_file->transactions); |
list_add_tail(&transaction->list, &fusd_file->transactions); |
up(&fusd_file->transactions_sem); |
up(&fusd_file->transactions_sem); |
|
|
if(out_transaction != NULL) |
if(out_transaction != NULL) |
*out_transaction = transaction; |
*out_transaction = transaction; |
|
|
return 0; |
return 0; |
} | } |
| |
STATIC void fusd_cleanup_transaction(fusd_file_t *fusd_file, struct fusd_transaction* transaction) | STATIC void fusd_cleanup_transaction(fusd_file_t *fusd_file, struct fusd_transaction* transaction) |
{ | { |
free_fusd_msg(&transaction->msg_in); |
free_fusd_msg(&transaction->msg_in); |
fusd_remove_transaction(fusd_file, transaction); |
fusd_remove_transaction(fusd_file, transaction); |
} | } |
| |
STATIC void fusd_remove_transaction(fusd_file_t *fusd_file, struct fusd_transaction* transaction) | STATIC void fusd_remove_transaction(fusd_file_t *fusd_file, struct fusd_transaction* transaction) |
{ | { |
down(&fusd_file->transactions_sem); |
down(&fusd_file->transactions_sem); |
list_del(&transaction->list); |
list_del(&transaction->list); |
up(&fusd_file->transactions_sem); |
up(&fusd_file->transactions_sem); |
|
|
KFREE(transaction); |
KFREE(transaction); |
} | } |
| |
STATIC struct fusd_transaction* fusd_find_transaction(fusd_file_t *fusd_file, int transid) | STATIC struct fusd_transaction* fusd_find_transaction(fusd_file_t *fusd_file, int transid) |
{ | { |
struct list_head* i; |
struct list_head* i; |
down(&fusd_file->transactions_sem); |
down(&fusd_file->transactions_sem); |
list_for_each(i, &fusd_file->transactions) |
list_for_each(i, &fusd_file->transactions) |
{ |
{ |
struct fusd_transaction* transaction = list_entry(i, struct fusd_transaction, list); |
struct fusd_transaction* transaction = list_entry(i, struct fusd_transaction, list); |
if(transaction->transid == transid) |
if(transaction->transid == transid) |
{ |
{ |
up(&fusd_file->transactions_sem); |
up(&fusd_file->transactions_sem); |
return transaction; |
return transaction; |
} |
} |
} |
} |
up(&fusd_file->transactions_sem); |
up(&fusd_file->transactions_sem); |
return NULL; |
return NULL; |
} | } |
| |
STATIC struct fusd_transaction* fusd_find_transaction_by_pid(fusd_file_t *fusd_file, int pid) | STATIC struct fusd_transaction* fusd_find_transaction_by_pid(fusd_file_t *fusd_file, int pid) |
{ | { |
struct list_head* i; |
struct list_head* i; |
down(&fusd_file->transactions_sem); |
down(&fusd_file->transactions_sem); |
list_for_each(i, &fusd_file->transactions) |
list_for_each(i, &fusd_file->transactions) |
{ |
{ |
struct fusd_transaction* transaction = list_entry(i, struct fusd_transaction, list); |
struct fusd_transaction* transaction = list_entry(i, struct fusd_transaction, list); |
if(transaction->pid == pid) |
if(transaction->pid == pid) |
{ |
{ |
up(&fusd_file->transactions_sem); |
up(&fusd_file->transactions_sem); |
return transaction; |
return transaction; |
} |
} |
} |
} |
up(&fusd_file->transactions_sem); |
up(&fusd_file->transactions_sem); |
return NULL; |
return NULL; |
} | } |
| |
STATIC ssize_t fusd_client_write(struct file *file, | STATIC ssize_t fusd_client_write(struct file *file, |
const char *buffer, |
const char *buffer, |
size_t length, |
size_t length, |
loff_t *offset) |
loff_t *offset) |
{ |
{ |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
fusd_msg_t fusd_msg; |
fusd_msg_t fusd_msg; |
fusd_msg_t *reply = NULL; |
fusd_msg_t *reply = NULL; |
int retval = -EPIPE; |
int retval = -EPIPE; |
struct fusd_transaction* transaction; |
struct fusd_transaction* transaction; |
|
|
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
|
|
RDEBUG(3, "got a write on /dev/%s (owned by pid %d) from pid %d", |
RDEBUG(3, "got a write on /dev/%s (owned by pid %d) from pid %d", |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_WRITE); |
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_WRITE); |
if (transaction && transaction->size == length) |
if (transaction && transaction->size == length) |
{ |
{ |
RDEBUG(2, "Incomplete I/O transaction %ld thrown out, as the transaction's size of %d bytes was not equal to " |
RDEBUG(2, "Incomplete I/O transaction %ld thrown out, as the transaction's size of %d bytes was not equal to " |
"the retry's size of %d bytes", transaction->transid, transaction->size, (int) length); |
"the retry's size of %d bytes", transaction->transid, transaction->size, (int) length); |
|
|
fusd_cleanup_transaction(fusd_file, transaction); |
fusd_cleanup_transaction(fusd_file, transaction); |
transaction = NULL; |
transaction = NULL; |
} |
} |
if(transaction == NULL) |
if(transaction == NULL) |
{ |
{ |
if (length < 0) { |
if (length < 0) { |
RDEBUG(2, "fusd_client_write: got invalid length %d", (int) length); |
RDEBUG(2, "fusd_client_write: got invalid length %d", (int) length); |
retval = -EINVAL; |
retval = -EINVAL; |
goto done; |
goto done; |
} |
} |
|
|
if (length > MAX_RW_SIZE) |
if (length > MAX_RW_SIZE) |
length = MAX_RW_SIZE; |
length = MAX_RW_SIZE; |
|
|
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
|
|
/* sigh.. i guess zero length writes should be legal */ |
/* sigh.. i guess zero length writes should be legal */ |
if (length > 0) { |
if (length > 0) { |
if ((fusd_msg.data = VMALLOC(length)) == NULL) { |
if ((fusd_msg.data = VMALLOC(length)) == NULL) { |
retval = -ENOMEM; |
retval = -ENOMEM; |
goto done; |
goto done; |
} |
} |
|
|
if (copy_from_user(fusd_msg.data, buffer, length)) { |
if (copy_from_user(fusd_msg.data, buffer, length)) { |
retval = -EFAULT; |
retval = -EFAULT; |
goto done; |
goto done; |
} |
} |
fusd_msg.datalen = length; |
fusd_msg.datalen = length; |
} |
} |
|
|
fusd_msg.subcmd = FUSD_WRITE; |
fusd_msg.subcmd = FUSD_WRITE; |
fusd_msg.parm.fops_msg.length = length; |
fusd_msg.parm.fops_msg.length = length; |
|
|
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
goto done; |
goto done; |
} |
} |
/* todo: fix transid on restart */ |
/* todo: fix transid on restart */ |
retval = fusd_fops_call_wait(fusd_file, &reply, transaction); |
retval = fusd_fops_call_wait(fusd_file, &reply, transaction); |
|
|
if (retval < 0 || reply == NULL) |
if (retval < 0 || reply == NULL) |
goto done; |
goto done; |
|
|
/* drivers should not write more bytes than they were asked to! */ |
/* drivers should not write more bytes than they were asked to! */ |
if (retval > length) { |
if (retval > length) { |
RDEBUG(1, "warning: /dev/%s driver (pid %d) returned %d bytes on write; " |
RDEBUG(1, "warning: /dev/%s driver (pid %d) returned %d bytes on write; " |
"the user only wanted %d", |
"the user only wanted %d", |
NAME(fusd_dev), fusd_dev->pid, retval, (int) length); |
NAME(fusd_dev), fusd_dev->pid, retval, (int) length); |
retval = length; |
retval = length; |
} |
} |
| |
*offset = reply->parm.fops_msg.offset; |
*offset = reply->parm.fops_msg.offset; |
| |
/* all done! */ |
/* all done! */ |
| |
done: | done: |
/* clear the writable bit of our cached poll state */ |
/* clear the writable bit of our cached poll state */ |
fusd_file->cached_poll_state &= ~(FUSD_NOTIFY_OUTPUT); |
fusd_file->cached_poll_state &= ~(FUSD_NOTIFY_OUTPUT); |
| |
free_fusd_msg(&reply); |
free_fusd_msg(&reply); |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
return retval; |
return retval; |
| |
invalid_file: | invalid_file: |
invalid_dev: | invalid_dev: |
RDEBUG(3, "got a read on client file from pid %d, driver has disappeared", |
RDEBUG(3, "got a read on client file from pid %d, driver has disappeared", |
current->pid); |
current->pid); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
STATIC int fusd_client_ioctl(struct inode *inode, struct file *file, | STATIC int fusd_client_ioctl(struct inode *inode, struct file *file, |
unsigned int cmd, unsigned long arg) |
unsigned int cmd, unsigned long arg) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
fusd_msg_t fusd_msg, *reply = NULL; |
fusd_msg_t fusd_msg, *reply = NULL; |
int retval = -EPIPE, dir, length; |
int retval = -EPIPE, dir, length; |
struct fusd_transaction* transaction; |
struct fusd_transaction* transaction; |
|
|
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
|
|
RDEBUG(3, "got an ioctl on /dev/%s (owned by pid %d) from pid %d", |
RDEBUG(3, "got an ioctl on /dev/%s (owned by pid %d) from pid %d", |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
dir = _IOC_DIR(cmd); |
dir = _IOC_DIR(cmd); |
length = _IOC_SIZE(cmd); |
length = _IOC_SIZE(cmd); |
|
|
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_IOCTL); |
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_IOCTL); |
// todo: Check to make sure the transaction is for the same IOCTL |
// todo: Check to make sure the transaction is for the same IOCTL |
|
|
if(transaction == NULL) |
if(transaction == NULL) |
{ |
{ |
/* if we're trying to read or write, make sure length is sane */ |
/* if we're trying to read or write, make sure length is sane */ |
if ((dir & (_IOC_WRITE | _IOC_READ)) && |
if ((dir & (_IOC_WRITE | _IOC_READ)) && |
(length <= 0 || length > MAX_RW_SIZE)) |
(length <= 0 || length > MAX_RW_SIZE)) |
{ |
{ |
RDEBUG(2, "client ioctl got crazy IOC_SIZE of %d", length); |
RDEBUG(2, "client ioctl got crazy IOC_SIZE of %d", length); |
retval = -EINVAL; |
retval = -EINVAL; |
goto done; |
goto done; |
} |
} |
|
|
/* fill the struct */ |
/* fill the struct */ |
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
fusd_msg.subcmd = FUSD_IOCTL; |
fusd_msg.subcmd = FUSD_IOCTL; |
fusd_msg.parm.fops_msg.cmd = cmd; |
fusd_msg.parm.fops_msg.cmd = cmd; |
fusd_msg.parm.fops_msg.arg = arg; |
fusd_msg.parm.fops_msg.arg = arg; |
|
|
/* get the data if user is trying to write to the driver */ |
/* get the data if user is trying to write to the driver */ |
if (dir & _IOC_WRITE) { |
if (dir & _IOC_WRITE) { |
if ((fusd_msg.data = VMALLOC(length)) == NULL) { |
if ((fusd_msg.data = VMALLOC(length)) == NULL) { |
RDEBUG(2, "can't vmalloc for client ioctl!"); |
RDEBUG(2, "can't vmalloc for client ioctl!"); |
retval = -ENOMEM; |
retval = -ENOMEM; |
goto done; |
goto done; |
} |
} |
|
|
if (copy_from_user(fusd_msg.data, (void *) arg, length)) { |
if (copy_from_user(fusd_msg.data, (void *) arg, length)) { |
retval = -EFAULT; |
retval = -EFAULT; |
goto done; |
goto done; |
} |
} |
fusd_msg.datalen = length; |
fusd_msg.datalen = length; |
} |
} |
|
|
/* send request to the driver */ |
/* send request to the driver */ |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
goto done; |
goto done; |
} |
} |
/* get the response */ |
/* get the response */ |
/* todo: fix transid on restart */ |
/* todo: fix transid on restart */ |
if ((retval = fusd_fops_call_wait(fusd_file, &reply, transaction)) < 0 || reply == NULL) |
if ((retval = fusd_fops_call_wait(fusd_file, &reply, transaction)) < 0 || reply == NULL) |
goto done; |
goto done; |
|
|
/* if user is trying to read from the driver, copy data back */ |
/* if user is trying to read from the driver, copy data back */ |
if (dir & _IOC_READ) { |
if (dir & _IOC_READ) { |
if (reply->data == NULL || reply->datalen != length) { |
if (reply->data == NULL || reply->datalen != length) { |
RDEBUG(2, "client_ioctl read reply with screwy data (%d, %d)", |
RDEBUG(2, "client_ioctl read reply with screwy data (%d, %d)", |
reply->datalen, length); |
reply->datalen, length); |
retval = -EIO; |
retval = -EIO; |
goto done; |
goto done; |
} |
} |
if (copy_to_user((void *)arg, reply->data, length)) { |
if (copy_to_user((void *)arg, reply->data, length)) { |
retval = -EFAULT; |
retval = -EFAULT; |
goto done; |
goto done; |
} |
} |
} |
} |
| |
/* all done! */ |
/* all done! */ |
done: | done: |
free_fusd_msg(&reply); |
free_fusd_msg(&reply); |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
return retval; |
return retval; |
| |
invalid_file: | invalid_file: |
invalid_dev: | invalid_dev: |
RDEBUG(3, "got a read on client file from pid %d, driver has disappeared", |
RDEBUG(3, "got a read on client file from pid %d, driver has disappeared", |
current->pid); |
current->pid); |
return -EPIPE; |
return -EPIPE; |
} | } |
static void fusd_client_mm_open(struct vm_area_struct * vma); | static void fusd_client_mm_open(struct vm_area_struct * vma); |
static void fusd_client_mm_close(struct vm_area_struct * vma); | static void fusd_client_mm_close(struct vm_area_struct * vma); |
static struct page* fusd_client_nopage(struct vm_area_struct* vma, unsigned long address, int* type); | static struct page* fusd_client_nopage(struct vm_area_struct* vma, unsigned long address, int* type); |
static struct vm_operations_struct fusd_remap_vm_ops = | static struct vm_operations_struct fusd_remap_vm_ops = |
{ | { |
open: fusd_client_mm_open, |
open: fusd_client_mm_open, |
close: fusd_client_mm_close, |
close: fusd_client_mm_close, |
nopage: fusd_client_nopage, |
nopage: fusd_client_nopage, |
}; | }; |
| |
struct fusd_mmap_instance | struct fusd_mmap_instance |
{ | { |
fusd_dev_t* fusd_dev; |
fusd_dev_t* fusd_dev; |
fusd_file_t* fusd_file; |
fusd_file_t* fusd_file; |
unsigned long addr; |
unsigned long addr; |
int size; |
int size; |
atomic_t refcount; |
atomic_t refcount; |
}; | }; |
| |
static void fusd_client_mm_open(struct vm_area_struct * vma) | static void fusd_client_mm_open(struct vm_area_struct * vma) |
{ | { |
struct fusd_mmap_instance* mmap_instance = (struct fusd_mmap_instance*) vma->vm_private_data; |
struct fusd_mmap_instance* mmap_instance = (struct fusd_mmap_instance*) vma->vm_private_data; |
atomic_inc(&mmap_instance->refcount); |
atomic_inc(&mmap_instance->refcount); |
|
|
} | } |
| |
static void fusd_client_mm_close(struct vm_area_struct * vma) | static void fusd_client_mm_close(struct vm_area_struct * vma) |
{ | { |
struct fusd_mmap_instance* mmap_instance = (struct fusd_mmap_instance*) vma->vm_private_data; |
struct fusd_mmap_instance* mmap_instance = (struct fusd_mmap_instance*) vma->vm_private_data; |
if(atomic_dec_and_test(&mmap_instance->refcount)) |
if(atomic_dec_and_test(&mmap_instance->refcount)) |
{ |
{ |
KFREE(mmap_instance); |
KFREE(mmap_instance); |
} |
} |
} | } |
| |
static int fusd_client_mmap(struct file *file, struct vm_area_struct * vma) | static int fusd_client_mmap(struct file *file, struct vm_area_struct * vma) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
struct fusd_transaction* transaction; |
struct fusd_transaction* transaction; |
fusd_msg_t fusd_msg, *reply = NULL; |
fusd_msg_t fusd_msg, *reply = NULL; |
int retval = -EPIPE; |
int retval = -EPIPE; |
struct fusd_mmap_instance* mmap_instance; |
struct fusd_mmap_instance* mmap_instance; |
|
|
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
|
|
RDEBUG(3, "got a mmap on /dev/%s (owned by pid %d) from pid %d", |
RDEBUG(3, "got a mmap on /dev/%s (owned by pid %d) from pid %d", |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
NAME(fusd_dev), fusd_dev->pid, current->pid); |
|
|
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_MMAP); |
transaction = fusd_find_incomplete_transaction(fusd_file, FUSD_MMAP); |
|
|
if(transaction == NULL) |
if(transaction == NULL) |
{ |
{ |
/* send the message */ |
/* send the message */ |
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
fusd_msg.subcmd = FUSD_MMAP; |
fusd_msg.subcmd = FUSD_MMAP; |
fusd_msg.parm.fops_msg.offset = vma->vm_pgoff << PAGE_SHIFT; |
fusd_msg.parm.fops_msg.offset = vma->vm_pgoff << PAGE_SHIFT; |
fusd_msg.parm.fops_msg.flags = vma->vm_flags; |
fusd_msg.parm.fops_msg.flags = vma->vm_flags; |
fusd_msg.parm.fops_msg.length = vma->vm_end - vma->vm_start; |
fusd_msg.parm.fops_msg.length = vma->vm_end - vma->vm_start; |
|
|
/* send message to userspace */ |
/* send message to userspace */ |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
if ((retval = fusd_fops_call_send(fusd_file, &fusd_msg, &transaction)) < 0) |
goto done; |
goto done; |
} |
} |
|
|
/* and wait for the reply */ |
/* and wait for the reply */ |
/* todo: store and retrieve the transid from the interrupted messsage */ |
/* todo: store and retrieve the transid from the interrupted messsage */ |
retval = fusd_fops_call_wait(fusd_file, &reply, transaction); |
retval = fusd_fops_call_wait(fusd_file, &reply, transaction); |
|
|
mmap_instance = |
mmap_instance = |
(struct fusd_mmap_instance*) KMALLOC(sizeof(struct fusd_mmap_instance), GFP_KERNEL); |
(struct fusd_mmap_instance*) KMALLOC(sizeof(struct fusd_mmap_instance), GFP_KERNEL); |
// todo: free this thing at some point |
// todo: free this thing at some point |
|
|
mmap_instance->fusd_dev = fusd_dev; |
mmap_instance->fusd_dev = fusd_dev; |
mmap_instance->fusd_file = fusd_file; |
mmap_instance->fusd_file = fusd_file; |
mmap_instance->addr = reply->parm.fops_msg.arg; |
mmap_instance->addr = reply->parm.fops_msg.arg; |
mmap_instance->size = reply->parm.fops_msg.length; |
mmap_instance->size = reply->parm.fops_msg.length; |
atomic_set(&mmap_instance->refcount, 0); |
atomic_set(&mmap_instance->refcount, 0); |
|
|
retval = reply->parm.fops_msg.retval; |
retval = reply->parm.fops_msg.retval; |
|
|
vma->vm_private_data = mmap_instance; |
vma->vm_private_data = mmap_instance; |
vma->vm_ops = &fusd_remap_vm_ops; |
vma->vm_ops = &fusd_remap_vm_ops; |
vma->vm_flags |= VM_RESERVED; |
vma->vm_flags |= VM_RESERVED; |
|
|
fusd_client_mm_open(vma); |
fusd_client_mm_open(vma); |
|
|
done: | done: |
free_fusd_msg(&reply); |
free_fusd_msg(&reply); |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
return retval; |
return retval; |
| |
invalid_file: | invalid_file: |
invalid_dev: | invalid_dev: |
RDEBUG(3, "got a mmap on client file from pid %d, driver has disappeared", |
RDEBUG(3, "got a mmap on client file from pid %d, driver has disappeared", |
current->pid); |
current->pid); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
static struct page* fusd_client_nopage(struct vm_area_struct* vma, unsigned long address, | static struct page* fusd_client_nopage(struct vm_area_struct* vma, unsigned long address, |
int* type) |
int* type) |
{ | { |
struct fusd_mmap_instance* mmap_instance = (struct fusd_mmap_instance*) vma->vm_private_data; |
struct fusd_mmap_instance* mmap_instance = (struct fusd_mmap_instance*) vma->vm_private_data; |
unsigned long offset; |
unsigned long offset; |
struct page *page = NOPAGE_SIGBUS; |
struct page *page = NOPAGE_SIGBUS; |
int result; |
int result; |
offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); |
offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); |
// todo: worry about size |
// todo: worry about size |
if(offset > mmap_instance->size) |
if(offset > mmap_instance->size) |
goto out; |
goto out; |
|
|
down_read(&mmap_instance->fusd_dev->task->mm->mmap_sem); |
down_read(&mmap_instance->fusd_dev->task->mm->mmap_sem); |
result = get_user_pages(mmap_instance->fusd_dev->task, mmap_instance->fusd_dev->task->mm, mmap_instance->addr + offset, 1, 1, 0, &page, NULL); |
result = get_user_pages(mmap_instance->fusd_dev->task, mmap_instance->fusd_dev->task->mm, mmap_instance->addr + offset, 1, 1, 0, &page, NULL); |
up_read(&mmap_instance->fusd_dev->task->mm->mmap_sem); |
up_read(&mmap_instance->fusd_dev->task->mm->mmap_sem); |
|
|
|
|
if(PageAnon(page)) |
if(PageAnon(page)) |
{ |
{ |
RDEBUG(2, "Cannot mmap anonymous pages. Be sure to allocate your shared buffer with MAP_SHARED | MAP_ANONYMOUS"); |
RDEBUG(2, "Cannot mmap anonymous pages. Be sure to allocate your shared buffer with MAP_SHARED | MAP_ANONYMOUS"); |
return NOPAGE_SIGBUS; |
return NOPAGE_SIGBUS; |
} |
} |
|
|
if(result > 0) |
if(result > 0) |
{ |
{ |
get_page(page); |
get_page(page); |
if (type) |
if (type) |
*type = VM_FAULT_MINOR; |
*type = VM_FAULT_MINOR; |
} |
} |
out: | out: |
return page; |
return page; |
| |
| |
} | } |
|
|
* The design of poll for clients is a bit subtle. | * The design of poll for clients is a bit subtle. |
* | * |
* We don't want the select() call itself to block, so we keep a cache | * We don't want the select() call itself to block, so we keep a cache |
* of the most recently known state supplied by the driver. The cache |
* of the most recently known state supplied by the driver. The cache |
* is initialized to 0 (meaning: nothing readable/writable). | * is initialized to 0 (meaning: nothing readable/writable). |
* | * |
* When a poll comes in, we do a non-blocking (!) dispatch of a | * When a poll comes in, we do a non-blocking (!) dispatch of a |
* command telling the driver "This is the state we have cached, reply | * command telling the driver "This is the state we have cached, reply |
* to this call when the state changes.", and then immediately return | * to this call when the state changes.", and then immediately return |
* the cached state. We tell the kernel's select to sleep on our |
* the cached state. We tell the kernel's select to sleep on our |
* poll_wait wait queue. | * poll_wait wait queue. |
* | * |
* When the driver replies, we update our cached info and wake up the | * When the driver replies, we update our cached info and wake up the |
* wait queue. Waking up the wait queue will most likely immediately |
* wait queue. Waking up the wait queue will most likely immediately |
* effect a poll again, in which case we will reply whatever we just | * effect a poll again, in which case we will reply whatever we just |
* cached from the driver. | * cached from the driver. |
* |
* |
*/ | */ |
STATIC unsigned int fusd_client_poll(struct file *file, poll_table *wait) | STATIC unsigned int fusd_client_poll(struct file *file, poll_table *wait) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
int kernel_bits = 0; |
int kernel_bits = 0; |
int send_poll = 0; |
int send_poll = 0; |
|
|
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
GET_FUSD_FILE_AND_DEV(file->private_data, fusd_file, fusd_dev); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_FILE(fusd_file); |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
|
|
RDEBUG(3, "got a select on /dev/%s (owned by pid %d) from pid %d, cps=%d", |
RDEBUG(3, "got a select on /dev/%s (owned by pid %d) from pid %d, cps=%d", |
NAME(fusd_dev), fusd_dev->pid, current->pid, |
NAME(fusd_dev), fusd_dev->pid, current->pid, |
fusd_file->cached_poll_state); |
fusd_file->cached_poll_state); |
|
|
poll_wait(file, &fusd_file->poll_wait, wait); |
poll_wait(file, &fusd_file->poll_wait, wait); |
|
|
/* |
/* |
* If our currently cached poll state is not the same as the |
* If our currently cached poll state is not the same as the |
* most-recently-sent polldiff request, then, dispatch a new |
* most-recently-sent polldiff request, then, dispatch a new |
* request. (We DO NOT wait for a reply, but just dispatch the |
* request. (We DO NOT wait for a reply, but just dispatch the |
* request). |
* request). |
* |
* |
* Also, don't send a new polldiff if the most recent one resulted |
* Also, don't send a new polldiff if the most recent one resulted |
* in an error. |
* in an error. |
*/ |
*/ |
if (fusd_file->last_poll_sent != fusd_file->cached_poll_state && |
if (fusd_file->last_poll_sent != fusd_file->cached_poll_state && |
fusd_file->cached_poll_state >= 0) { |
fusd_file->cached_poll_state >= 0) { |
RDEBUG(3, "sending polldiff request because lps=%d, cps=%d", |
RDEBUG(3, "sending polldiff request because lps=%d, cps=%d", |
fusd_file->last_poll_sent, fusd_file->cached_poll_state); |
fusd_file->last_poll_sent, fusd_file->cached_poll_state); |
send_poll = 1; |
send_poll = 1; |
fusd_file->last_poll_sent = fusd_file->cached_poll_state; |
fusd_file->last_poll_sent = fusd_file->cached_poll_state; |
} |
} |
|
|
/* compute what to return for the state we had cached, converting to |
/* compute what to return for the state we had cached, converting to |
* bits that have meaning to the kernel */ |
* bits that have meaning to the kernel */ |
if (fusd_file->cached_poll_state > 0) { |
if (fusd_file->cached_poll_state > 0) { |
if (fusd_file->cached_poll_state & FUSD_NOTIFY_INPUT) |
if (fusd_file->cached_poll_state & FUSD_NOTIFY_INPUT) |
kernel_bits |= POLLIN; |
kernel_bits |= POLLIN; |
if (fusd_file->cached_poll_state & FUSD_NOTIFY_OUTPUT) |
if (fusd_file->cached_poll_state & FUSD_NOTIFY_OUTPUT) |
kernel_bits |= POLLOUT; |
kernel_bits |= POLLOUT; |
if (fusd_file->cached_poll_state & FUSD_NOTIFY_EXCEPT) |
if (fusd_file->cached_poll_state & FUSD_NOTIFY_EXCEPT) |
kernel_bits |= POLLPRI; |
kernel_bits |= POLLPRI; |
} |
} |
|
|
/* Now that we've committed to sending the poll, etc., it should be |
/* Now that we've committed to sending the poll, etc., it should be |
* safe to unlock the device */ |
* safe to unlock the device */ |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
|
|
if (send_poll) { |
if (send_poll) { |
fusd_msg_t fusd_msg; |
fusd_msg_t fusd_msg; |
|
|
init_fusd_msg(&fusd_msg); |
init_fusd_msg(&fusd_msg); |
fusd_msg.cmd = FUSD_FOPS_NONBLOCK; |
fusd_msg.cmd = FUSD_FOPS_NONBLOCK; |
fusd_msg.subcmd = FUSD_POLL_DIFF; |
fusd_msg.subcmd = FUSD_POLL_DIFF; |
fusd_msg.parm.fops_msg.cmd = fusd_file->cached_poll_state; |
fusd_msg.parm.fops_msg.cmd = fusd_file->cached_poll_state; |
if (fusd_fops_call_send(fusd_file, &fusd_msg, NULL) < 0) { |
if (fusd_fops_call_send(fusd_file, &fusd_msg, NULL) < 0) { |
/* If poll dispatched failed, set back to -1 so we try again. |
/* If poll dispatched failed, set back to -1 so we try again. |
* Not a race (I think), since sending an *extra* polldiff never |
* Not a race (I think), since sending an *extra* polldiff never |
* hurts anything. */ |
* hurts anything. */ |
fusd_file->last_poll_sent = -1; |
fusd_file->last_poll_sent = -1; |
} |
} |
} |
} |
return kernel_bits; |
return kernel_bits; |
| |
zombie_dev: | zombie_dev: |
/* might jump here from LOCK_FUSD_DEV */ |
/* might jump here from LOCK_FUSD_DEV */ |
UNLOCK_FUSD_FILE(fusd_file); |
UNLOCK_FUSD_FILE(fusd_file); |
invalid_dev: | invalid_dev: |
invalid_file: | invalid_file: |
RDEBUG(3, "got a select on client file from pid %d, driver has disappeared", |
RDEBUG(3, "got a select on client file from pid %d, driver has disappeared", |
current->pid); |
current->pid); |
return POLLPRI; |
return POLLPRI; |
} | } |
| |
| |
| |
STATIC struct file_operations fusd_client_fops = { | STATIC struct file_operations fusd_client_fops = { |
owner: THIS_MODULE, |
owner: THIS_MODULE, |
open: fusd_client_open, |
open: fusd_client_open, |
release: fusd_client_release, |
release: fusd_client_release, |
read: fusd_client_read, |
read: fusd_client_read, |
write: fusd_client_write, |
write: fusd_client_write, |
ioctl: fusd_client_ioctl, |
ioctl: fusd_client_ioctl, |
poll: fusd_client_poll, |
poll: fusd_client_poll, |
mmap: fusd_client_mmap |
mmap: fusd_client_mmap |
}; | }; |
| |
| |
|
|
| |
STATIC fusd_file_t *find_fusd_reply_file(fusd_dev_t *fusd_dev, fusd_msg_t *msg) | STATIC fusd_file_t *find_fusd_reply_file(fusd_dev_t *fusd_dev, fusd_msg_t *msg) |
{ | { |
/* first, try the hint */ |
/* first, try the hint */ |
int i = msg->parm.fops_msg.hint; |
int i = msg->parm.fops_msg.hint; |
if (i >= 0 && |
if (i >= 0 && |
i < fusd_dev->num_files && |
i < fusd_dev->num_files && |
fusd_dev->files[i] == msg->parm.fops_msg.fusd_file) |
fusd_dev->files[i] == msg->parm.fops_msg.fusd_file) |
{ |
{ |
RDEBUG(15, "find_fusd_reply_file: hint worked"); |
RDEBUG(15, "find_fusd_reply_file: hint worked"); |
} else { |
} else { |
/* hint didn't work, fall back to a search of the whole array */ |
/* hint didn't work, fall back to a search of the whole array */ |
i = find_fusd_file(fusd_dev, msg->parm.fops_msg.fusd_file); |
i = find_fusd_file(fusd_dev, msg->parm.fops_msg.fusd_file); |
RDEBUG(15, "find_fusd_reply_file: hint failed"); |
RDEBUG(15, "find_fusd_reply_file: hint failed"); |
} |
} |
|
|
/* we couldn't find anyone waiting for this message! */ |
/* we couldn't find anyone waiting for this message! */ |
if (i < 0) { |
if (i < 0) { |
return NULL; |
return NULL; |
} else { |
} else { |
return fusd_dev->files[i]; |
return fusd_dev->files[i]; |
} |
} |
} | } |
| |
| |
/* Process an incoming reply to a message dispatched by | /* Process an incoming reply to a message dispatched by |
* fusd_fops_call. Called by fusd_write when a driver writes to |
* fusd_fops_call. Called by fusd_write when a driver writes to |
* /dev/fusd. */ | * /dev/fusd. */ |
STATIC int fusd_fops_reply(fusd_dev_t *fusd_dev, fusd_msg_t *msg) | STATIC int fusd_fops_reply(fusd_dev_t *fusd_dev, fusd_msg_t *msg) |
{ | { |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
struct fusd_transaction *transaction; |
struct fusd_transaction *transaction; |
| |
/* figure out the index of the file we are replying to. usually |
/* figure out the index of the file we are replying to. usually |
* very fast (uses a hint) */ |
* very fast (uses a hint) */ |
if ((fusd_file = find_fusd_reply_file(fusd_dev, msg)) == NULL) { |
if ((fusd_file = find_fusd_reply_file(fusd_dev, msg)) == NULL) { |
RDEBUG(2, "fusd_fops_reply: got a reply on /dev/%s with no connection", |
RDEBUG(2, "fusd_fops_reply: got a reply on /dev/%s with no connection", |
NAME(fusd_dev)); |
NAME(fusd_dev)); |
goto discard; |
goto discard; |
} |
} |
| |
/* make sure this is not an old reply going to an old instance that's gone */ |
/* make sure this is not an old reply going to an old instance that's gone */ |
/* todo: kor fix this */ |
/* todo: kor fix this */ |
/* | /* |
if (fusd_file->fusd_dev_version != fusd_dev->version || |
if (fusd_file->fusd_dev_version != fusd_dev->version || |
msg->parm.fops_msg.transid != fusd_file->transid_outstanding) { |
msg->parm.fops_msg.transid != fusd_file->transid_outstanding) { |
RDEBUG(2, "fusd_fops_reply: got an old message, discarding"); |
RDEBUG(2, "fusd_fops_reply: got an old message, discarding"); |
goto discard; |
goto discard; |
}*/ |
}*/ |
|
|
transaction = fusd_find_transaction(fusd_file, msg->parm.fops_msg.transid); |
transaction = fusd_find_transaction(fusd_file, msg->parm.fops_msg.transid); |
if(transaction == NULL) |
if(transaction == NULL) |
{ |
{ |
RDEBUG(2, "fusd_fops_reply: No transaction found with transid %ld", msg->parm.fops_msg.transid); |
RDEBUG(2, "fusd_fops_reply: No transaction found with transid %ld", msg->parm.fops_msg.transid); |
goto discard; |
goto discard; |
} |
} |
|
|
RDEBUG(10, "fusd_fops_reply: /dev/%s completed transid %ld (retval %d)", |
RDEBUG(10, "fusd_fops_reply: /dev/%s completed transid %ld (retval %d)", |
NAME(fusd_dev), msg->parm.fops_msg.transid, |
NAME(fusd_dev), msg->parm.fops_msg.transid, |
(int) msg->parm.fops_msg.retval); |
(int) msg->parm.fops_msg.retval); |
| |
transaction->msg_in = msg; |
transaction->msg_in = msg; |
mb(); |
mb(); |
| |
WAKE_UP_INTERRUPTIBLE_SYNC(&fusd_file->file_wait); |
WAKE_UP_INTERRUPTIBLE_SYNC(&fusd_file->file_wait); |
| |
return 0; |
return 0; |
| |
discard: | discard: |
if (msg->subcmd == FUSD_OPEN && msg->parm.fops_msg.retval == 0) { |
if (msg->subcmd == FUSD_OPEN && msg->parm.fops_msg.retval == 0) { |
fusd_forge_close(msg, fusd_dev); |
fusd_forge_close(msg, fusd_dev); |
return 0; |
return 0; |
} else { |
} else { |
return -EPIPE; |
return -EPIPE; |
} |
} |
} | } |
| |
| |
/* special function to process responses to POLL_DIFF */ | /* special function to process responses to POLL_DIFF */ |
STATIC int fusd_polldiff_reply(fusd_dev_t *fusd_dev, fusd_msg_t *msg) | STATIC int fusd_polldiff_reply(fusd_dev_t *fusd_dev, fusd_msg_t *msg) |
{ | { |
fusd_file_t *fusd_file; |
fusd_file_t *fusd_file; |
| |
/* figure out the index of the file we are replying to. usually |
/* figure out the index of the file we are replying to. usually |
* very fast (uses a hint) */ |
* very fast (uses a hint) */ |
if ((fusd_file = find_fusd_reply_file(fusd_dev, msg)) == NULL) |
if ((fusd_file = find_fusd_reply_file(fusd_dev, msg)) == NULL) |
return -EPIPE; |
return -EPIPE; |
| |
/* record the poll state returned. convert all negative retvals to -1. */ |
/* record the poll state returned. convert all negative retvals to -1. */ |
if ((fusd_file->cached_poll_state = msg->parm.fops_msg.retval) < 0) |
if ((fusd_file->cached_poll_state = msg->parm.fops_msg.retval) < 0) |
fusd_file->cached_poll_state = -1; |
fusd_file->cached_poll_state = -1; |
| |
RDEBUG(3, "got updated poll state from /dev/%s driver: %d", NAME(fusd_dev), |
RDEBUG(3, "got updated poll state from /dev/%s driver: %d", NAME(fusd_dev), |
fusd_file->cached_poll_state); |
fusd_file->cached_poll_state); |
| |
/* since the client has returned the polldiff we sent, set |
/* since the client has returned the polldiff we sent, set |
* last_poll_sent to -1, so that we'll send a polldiff request on |
* last_poll_sent to -1, so that we'll send a polldiff request on |
* the next select. */ |
* the next select. */ |
fusd_file->last_poll_sent = -1; |
fusd_file->last_poll_sent = -1; |
| |
/* wake up select's queue so that a new polldiff is generated */ |
/* wake up select's queue so that a new polldiff is generated */ |
wake_up_interruptible(&fusd_file->poll_wait); |
wake_up_interruptible(&fusd_file->poll_wait); |
| |
return 0; |
return 0; |
} | } |
| |
STATIC int fusd_register_device(fusd_dev_t *fusd_dev, | STATIC int fusd_register_device(fusd_dev_t *fusd_dev, |
register_msg_t register_msg) |
register_msg_t register_msg) |
{ | { |
int error = 0; |
int error = 0; |
struct list_head *tmp; |
struct list_head *tmp; |
int dev_id; |
int dev_id; |
|
|
/* make sure args are valid */ |
/* make sure args are valid */ |
if (fusd_dev == NULL) { |
if (fusd_dev == NULL) { |
RDEBUG(0, "fusd_register_device: bug in arguments!"); |
RDEBUG(0, "fusd_register_device: bug in arguments!"); |
return -EINVAL; |
return -EINVAL; |
} |
} |
|
|
/* user can only register one device per instance */ |
/* user can only register one device per instance */ |
// if (fusd_dev->handle != 0) |
// if (fusd_dev->handle != 0) |
// return -EBUSY; |
// return -EBUSY; |
|
|
register_msg.name[FUSD_MAX_NAME_LENGTH] = '\0'; |
register_msg.name[FUSD_MAX_NAME_LENGTH] = '\0'; |
|
|
/* make sure that there isn't already a device by this name */ |
/* make sure that there isn't already a device by this name */ |
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
list_for_each(tmp, &fusd_devlist_head) { |
list_for_each(tmp, &fusd_devlist_head) { |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
|
|
if (d && d->name && !d->zombie && !strcmp(d->name, register_msg.name)) { |
if (d && d->name && !d->zombie && !strcmp(d->name, register_msg.name)) { |
error = -EEXIST; |
error = -EEXIST; |
break; |
break; |
} |
} |
} |
} |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
|
|
if (error) |
if (error) |
return error; |
return error; |
|
|
|
|
/* allocate memory for the name, and copy */ |
/* allocate memory for the name, and copy */ |
if ((fusd_dev->name = KMALLOC(strlen(register_msg.name)+1, GFP_KERNEL)) == NULL) { |
if ((fusd_dev->name = KMALLOC(strlen(register_msg.name)+1, GFP_KERNEL)) == NULL) { |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
return -ENOMEM; |
return -ENOMEM; |
} |
} |
|
|
strcpy(fusd_dev->name, register_msg.name); |
strcpy(fusd_dev->name, register_msg.name); |
|
|
/* allocate memory for the class name, and copy */ |
/* allocate memory for the class name, and copy */ |
if ((fusd_dev->class_name = KMALLOC(strlen(register_msg.clazz)+1, GFP_KERNEL)) == NULL) { |
if ((fusd_dev->class_name = KMALLOC(strlen(register_msg.clazz)+1, GFP_KERNEL)) == NULL) { |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
return -ENOMEM; |
return -ENOMEM; |
} |
} |
|
|
strcpy(fusd_dev->class_name, register_msg.clazz); |
strcpy(fusd_dev->class_name, register_msg.clazz); |
|
|
/* allocate memory for the class name, and copy */ |
/* allocate memory for the class name, and copy */ |
if ((fusd_dev->dev_name = KMALLOC(strlen(register_msg.devname)+1, GFP_KERNEL)) == NULL) { |
if ((fusd_dev->dev_name = KMALLOC(strlen(register_msg.devname)+1, GFP_KERNEL)) == NULL) { |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
return -ENOMEM; |
return -ENOMEM; |
} |
} |
|
|
strcpy(fusd_dev->dev_name, register_msg.devname); |
strcpy(fusd_dev->dev_name, register_msg.devname); |
|
|
dev_id = 0; |
dev_id = 0; |
|
|
if((error = alloc_chrdev_region(&dev_id, 0, 1, fusd_dev->name)) < 0) |
if((error = alloc_chrdev_region(&dev_id, 0, 1, fusd_dev->name)) < 0) |
{ |
{ |
printk("alloc_chrdev_region failed status: %d\n", error); |
printk("alloc_chrdev_region failed status: %d\n", error); |
goto register_failed; |
goto register_failed; |
} |
} |
|
|
fusd_dev->dev_id = dev_id; |
fusd_dev->dev_id = dev_id; |
|
|
#ifdef CONFIG_DEVFS_FS |
#ifdef CONFIG_DEVFS_FS |
if((error = devfs_mk_cdev(dev_id, S_IFCHR | register_msg.mode, fusd_dev->name)) < 0) |
if((error = devfs_mk_cdev(dev_id, S_IFCHR | register_msg.mode, fusd_dev->name)) < 0) |
{ |
{ |
printk("devfs_mk_cdev failed status: %d\n", error); |
printk("devfs_mk_cdev failed status: %d\n", error); |
goto register_failed2; |
goto register_failed2; |
} |
} |
#endif |
#endif |
|
|
fusd_dev->handle = cdev_alloc(); |
fusd_dev->handle = cdev_alloc(); |
if(fusd_dev->handle == NULL) |
if(fusd_dev->handle == NULL) |
{ |
{ |
error = -ENOMEM; |
error = -ENOMEM; |
goto register_failed3; |
goto register_failed3; |
} |
} |
|
|
fusd_dev->handle->owner = THIS_MODULE; |
fusd_dev->handle->owner = THIS_MODULE; |
fusd_dev->handle->ops = &fusd_client_fops; |
fusd_dev->handle->ops = &fusd_client_fops; |
|
|
kobject_set_name(&fusd_dev->handle->kobj, fusd_dev->name); |
kobject_set_name(&fusd_dev->handle->kobj, fusd_dev->name); |
|
|
if((error = cdev_add(fusd_dev->handle, dev_id, 1)) < 0) |
if((error = cdev_add(fusd_dev->handle, dev_id, 1)) < 0) |
{ |
{ |
printk("cdev_add failed status: %d\n", error); |
printk("cdev_add failed status: %d\n", error); |
kobject_put(&fusd_dev->handle->kobj); |
kobject_put(&fusd_dev->handle->kobj); |
goto register_failed3; |
goto register_failed3; |
} |
} |
|
|
// Hack to add my class to the sound class |
// Hack to add my class to the sound class |
if(strcmp("sound", register_msg.clazz) == 0) |
if(strcmp("sound", register_msg.clazz) == 0) |
{ |
{ |
fusd_dev->clazz = sound_class; |
fusd_dev->clazz = sound_class; |
fusd_dev->owns_class = 0; |
fusd_dev->owns_class = 0; |
} |
} |
else |
else |
{ |
{ |
fusd_dev->clazz = class_create(THIS_MODULE, fusd_dev->class_name); |
fusd_dev->clazz = class_create(THIS_MODULE, fusd_dev->class_name); |
if(IS_ERR(fusd_dev->clazz)) |
if(IS_ERR(fusd_dev->clazz)) |
{ |
{ |
error = PTR_ERR(fusd_dev->clazz); |
error = PTR_ERR(fusd_dev->clazz); |
goto register_failed4; |
goto register_failed4; |
} |
} |
fusd_dev->owns_class = 1; |
fusd_dev->owns_class = 1; |
} |
} |
|
|
fusd_dev->class_device = CLASS_DEVICE_CREATE(fusd_dev->clazz, NULL, fusd_dev->dev_id, NULL, fusd_dev->dev_name); |
fusd_dev->class_device = CLASS_DEVICE_CREATE(fusd_dev->clazz, NULL, fusd_dev->dev_id, NULL, fusd_dev->dev_name); |
if(fusd_dev->class_device == NULL) |
if(fusd_dev->class_device == NULL) |
{ |
{ |
error = PTR_ERR(fusd_dev->class_device); |
error = PTR_ERR(fusd_dev->class_device); |
printk("class_device_create failed status: %d\n", error); |
printk("class_device_create failed status: %d\n", error); |
goto register_failed5; |
goto register_failed5; |
} |
} |
|
|
/* make sure the registration was successful */ |
/* make sure the registration was successful */ |
/* |
/* |
if (fusd_dev->handle == 0) { |
if (fusd_dev->handle == 0) { |
error = -EIO; |
error = -EIO; |
goto register_failed; |
goto register_failed; |
} |
} |
*/ |
*/ |
|
|
/* remember the user's private data so we can pass it back later */ |
/* remember the user's private data so we can pass it back later */ |
fusd_dev->private_data = register_msg.device_info; |
fusd_dev->private_data = register_msg.device_info; |
|
|
/* everything ok */ |
/* everything ok */ |
fusd_dev->version = atomic_inc_and_ret(&last_version); |
fusd_dev->version = atomic_inc_and_ret(&last_version); |
RDEBUG(3, "pid %d registered /dev/%s v%ld", fusd_dev->pid, NAME(fusd_dev), |
RDEBUG(3, "pid %d registered /dev/%s v%ld", fusd_dev->pid, NAME(fusd_dev), |
fusd_dev->version); |
fusd_dev->version); |
wake_up_interruptible(&new_device_wait); |
wake_up_interruptible(&new_device_wait); |
return 0; |
return 0; |
| |
register_failed5: | register_failed5: |
class_destroy(fusd_dev->clazz); |
class_destroy(fusd_dev->clazz); |
register_failed4: | register_failed4: |
cdev_del(fusd_dev->handle); |
cdev_del(fusd_dev->handle); |
register_failed3: | register_failed3: |
#ifdef CONFIG_DEVFS_FS | #ifdef CONFIG_DEVFS_FS |
devfs_remove(fusd_dev->name); |
devfs_remove(fusd_dev->name); |
#endif | #endif |
register_failed2: |
/*register_failed2:*/ |
unregister_chrdev_region(dev_id, 1); |
unregister_chrdev_region(dev_id, 1); |
register_failed: | register_failed: |
KFREE(fusd_dev->name); |
KFREE(fusd_dev->name); |
fusd_dev->name = NULL; |
fusd_dev->name = NULL; |
return error; |
return error; |
} | } |
| |
| |
|
|
/* open() called on /dev/fusd itself */ | /* open() called on /dev/fusd itself */ |
STATIC int fusd_open(struct inode *inode, struct file *file) | STATIC int fusd_open(struct inode *inode, struct file *file) |
{ | { |
fusd_dev_t *fusd_dev = NULL; |
fusd_dev_t *fusd_dev = NULL; |
fusd_file_t **file_array = NULL; |
fusd_file_t **file_array = NULL; |
| |
/* keep the module from being unloaded during initialization! */ |
/* keep the module from being unloaded during initialization! */ |
//MOD_INC_USE_COUNT; |
//MOD_INC_USE_COUNT; |
| |
/* allocate memory for the device state */ |
/* allocate memory for the device state */ |
if ((fusd_dev = KMALLOC(sizeof(fusd_dev_t), GFP_KERNEL)) == NULL) |
if ((fusd_dev = KMALLOC(sizeof(fusd_dev_t), GFP_KERNEL)) == NULL) |
goto dev_malloc_failed; |
goto dev_malloc_failed; |
memset(fusd_dev, 0, sizeof(fusd_dev_t)); |
memset(fusd_dev, 0, sizeof(fusd_dev_t)); |
|
|
if ((file_array = fusd_dev_adjsize(fusd_dev)) == NULL) |
if ((file_array = fusd_dev_adjsize(fusd_dev)) == NULL) |
goto file_malloc_failed; |
goto file_malloc_failed; |
|
|
init_waitqueue_head(&fusd_dev->dev_wait); |
init_waitqueue_head(&fusd_dev->dev_wait); |
init_MUTEX(&fusd_dev->dev_sem); |
init_MUTEX(&fusd_dev->dev_sem); |
fusd_dev->magic = FUSD_DEV_MAGIC; |
fusd_dev->magic = FUSD_DEV_MAGIC; |
fusd_dev->pid = current->pid; |
fusd_dev->pid = current->pid; |
fusd_dev->task = current; |
fusd_dev->task = current; |
file->private_data = fusd_dev; |
file->private_data = fusd_dev; |
|
|
/* add to the list of valid devices */ |
/* add to the list of valid devices */ |
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
list_add(&fusd_dev->devlist, &fusd_devlist_head); |
list_add(&fusd_dev->devlist, &fusd_devlist_head); |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
| |
RDEBUG(3, "pid %d opened /dev/fusd", fusd_dev->pid); |
RDEBUG(3, "pid %d opened /dev/fusd", fusd_dev->pid); |
return 0; |
return 0; |
| |
file_malloc_failed: | file_malloc_failed: |
KFREE(fusd_dev); |
KFREE(fusd_dev); |
dev_malloc_failed: | dev_malloc_failed: |
RDEBUG(1, "out of memory in fusd_open!"); |
RDEBUG(1, "out of memory in fusd_open!"); |
//MOD_DEC_USE_COUNT; |
//MOD_DEC_USE_COUNT; |
return -ENOMEM; |
return -ENOMEM; |
} | } |
| |
| |
/* close() called on /dev/fusd itself. destroy the device that |
/* close() called on /dev/fusd itself. destroy the device that |
* was registered by it, if any. */ | * was registered by it, if any. */ |
STATIC int fusd_release(struct inode *inode, struct file *file) | STATIC int fusd_release(struct inode *inode, struct file *file) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
| |
GET_FUSD_DEV(file->private_data, fusd_dev); |
GET_FUSD_DEV(file->private_data, fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
| |
if (fusd_dev->pid != current->pid) { |
if (fusd_dev->pid != current->pid) { |
RDEBUG(2, "yikes!: when releasing device, pid mismatch"); |
RDEBUG(2, "yikes!: when releasing device, pid mismatch"); |
} |
} |
| |
RDEBUG(3, "pid %d closing /dev/fusd", current->pid); |
RDEBUG(3, "pid %d closing /dev/fusd", current->pid); |
| |
#if 0 | #if 0 |
/* This delay is needed to exercise the openrace.c race condition, |
/* This delay is needed to exercise the openrace.c race condition, |
* i.e. testing to make sure that our open_in_progress stuff works */ |
* i.e. testing to make sure that our open_in_progress stuff works */ |
{ |
{ |
int target = jiffies + 10*HZ; |
int target = jiffies + 10*HZ; |
|
|
RDEBUG(1, "starting to wait"); |
RDEBUG(1, "starting to wait"); |
while (jiffies < target) |
while (jiffies < target) |
schedule(); |
schedule(); |
RDEBUG(1, "stopping wait"); |
RDEBUG(1, "stopping wait"); |
} |
} |
#endif |
|
|
|
if(fusd_dev->handle) |
|
{ |
|
class_device_destroy(fusd_dev->clazz, fusd_dev->dev_id); |
|
if(fusd_dev->owns_class) |
|
{ |
|
class_destroy(fusd_dev->clazz); |
|
} |
|
cdev_del(fusd_dev->handle); |
|
#ifdef CONFIG_DEVFS_FS |
|
devfs_remove(fusd_dev->name); |
|
#endif | #endif |
unregister_chrdev_region(fusd_dev->dev_id, 1); |
|
} |
|
| |
/* mark the driver as being gone */ |
if(fusd_dev->handle) |
zombify_dev(fusd_dev); |
{ |
|
class_device_destroy(fusd_dev->clazz, fusd_dev->dev_id); |
|
if(fusd_dev->owns_class) |
|
{ |
|
class_destroy(fusd_dev->clazz); |
|
} |
|
cdev_del(fusd_dev->handle); |
|
#ifdef CONFIG_DEVFS_FS |
|
devfs_remove(fusd_dev->name); |
|
#endif |
|
unregister_chrdev_region(fusd_dev->dev_id, 1); |
|
} |
| |
/* ...and possibly free it. (Release lock if it hasn't been freed) */ |
/* mark the driver as being gone */ |
if (!maybe_free_fusd_dev(fusd_dev)) |
zombify_dev(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
|
| |
/* notify fusd_status readers that there has been a change in the |
/* ...and possibly free it. (Release lock if it hasn't been freed) */ |
* list of registered devices */ |
if (!maybe_free_fusd_dev(fusd_dev)) |
atomic_inc_and_ret(&last_version); |
UNLOCK_FUSD_DEV(fusd_dev); |
wake_up_interruptible(&new_device_wait); |
|
|
/* notify fusd_status readers that there has been a change in the |
|
* list of registered devices */ |
|
atomic_inc_and_ret(&last_version); |
|
wake_up_interruptible(&new_device_wait); |
| |
return 0; |
return 0; |
| |
zombie_dev: | zombie_dev: |
invalid_dev: | invalid_dev: |
RDEBUG(1, "invalid device found in fusd_release!!"); |
RDEBUG(1, "invalid device found in fusd_release!!"); |
return -ENODEV; |
return -ENODEV; |
} | } |
| |
| |
|
|
* (i.e., writes to the /dev/fusd control channel.) | * (i.e., writes to the /dev/fusd control channel.) |
*/ | */ |
STATIC ssize_t fusd_process_write(struct file *file, | STATIC ssize_t fusd_process_write(struct file *file, |
const char *user_msg_buffer, size_t user_msg_len, |
const char *user_msg_buffer, size_t user_msg_len, |
const char *user_data_buffer, size_t user_data_len) |
const char *user_data_buffer, size_t user_data_len) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_msg_t *msg = NULL; |
fusd_msg_t *msg = NULL; |
int retval = 0; |
int retval = 0; |
int yield = 0; |
int yield = 0; |
|
|
GET_FUSD_DEV(file->private_data, fusd_dev); |
GET_FUSD_DEV(file->private_data, fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
|
|
/* get the header from userspace (first make sure there's enough data) */ |
/* get the header from userspace (first make sure there's enough data) */ |
if (user_msg_len != sizeof(fusd_msg_t)) { |
if (user_msg_len != sizeof(fusd_msg_t)) { |
RDEBUG(6, "control channel got bad write of %d bytes (wanted %d)", |
RDEBUG(6, "control channel got bad write of %d bytes (wanted %d)", |
(int) user_msg_len, (int) sizeof(fusd_msg_t)); |
(int) user_msg_len, (int) sizeof(fusd_msg_t)); |
retval = -EINVAL; |
retval = -EINVAL; |
goto out_no_free; |
goto out_no_free; |
} |
} |
if ((msg = KMALLOC(sizeof(fusd_msg_t), GFP_KERNEL)) == NULL) { |
if ((msg = KMALLOC(sizeof(fusd_msg_t), GFP_KERNEL)) == NULL) { |
retval = -ENOMEM; |
retval = -ENOMEM; |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
goto out; |
goto out; |
} |
} |
memset(msg, 0, sizeof(fusd_msg_t)); |
memset(msg, 0, sizeof(fusd_msg_t)); |
|
|
if (copy_from_user(msg, user_msg_buffer, sizeof(fusd_msg_t))) { |
if (copy_from_user(msg, user_msg_buffer, sizeof(fusd_msg_t))) { |
retval = -EFAULT; |
retval = -EFAULT; |
goto out; |
goto out; |
} |
} |
msg->data = NULL; /* pointers from userspace have no meaning */ |
msg->data = NULL; /* pointers from userspace have no meaning */ |
|
|
/* check the magic number before acting on the message at all */ |
/* check the magic number before acting on the message at all */ |
if (msg->magic != FUSD_MSG_MAGIC) { |
if (msg->magic != FUSD_MSG_MAGIC) { |
RDEBUG(2, "got invalid magic number on /dev/fusd write from pid %d", |
RDEBUG(2, "got invalid magic number on /dev/fusd write from pid %d", |
current->pid); |
current->pid); |
retval = -EIO; |
retval = -EIO; |
goto out; |
goto out; |
} |
} |
|
|
/* now get data portion of the message */ |
/* now get data portion of the message */ |
if (user_data_len < 0 || user_data_len > MAX_RW_SIZE) { |
if (user_data_len < 0 || user_data_len > MAX_RW_SIZE) { |
RDEBUG(2, "fusd_process_write: got invalid length %d", (int) user_data_len); |
RDEBUG(2, "fusd_process_write: got invalid length %d", (int) user_data_len); |
retval = -EINVAL; |
retval = -EINVAL; |
goto out; |
goto out; |
} |
} |
if (msg->datalen != user_data_len) { |
if (msg->datalen != user_data_len) { |
RDEBUG(2, "msg->datalen(%d) != user_data_len(%d), sigh!", |
RDEBUG(2, "msg->datalen(%d) != user_data_len(%d), sigh!", |
msg->datalen, (int) user_data_len); |
msg->datalen, (int) user_data_len); |
retval = -EINVAL; |
retval = -EINVAL; |
goto out; |
goto out; |
} |
} |
if (user_data_len > 0) { |
if (user_data_len > 0) { |
if (user_data_buffer == NULL) { |
if (user_data_buffer == NULL) { |
RDEBUG(2, "msg->datalen and no data buffer, sigh!"); |
RDEBUG(2, "msg->datalen and no data buffer, sigh!"); |
retval = -EINVAL; |
retval = -EINVAL; |
goto out; |
goto out; |
} |
} |
if ((msg->data = VMALLOC(user_data_len)) == NULL) { |
if ((msg->data = VMALLOC(user_data_len)) == NULL) { |
retval = -ENOMEM; |
retval = -ENOMEM; |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
goto out; |
goto out; |
} |
} |
if (copy_from_user(msg->data, user_data_buffer, user_data_len)) { |
if (copy_from_user(msg->data, user_data_buffer, user_data_len)) { |
retval = -EFAULT; |
retval = -EFAULT; |
goto out; |
goto out; |
} |
} |
} |
} |
|
|
/* before device registration, the only command allowed is 'register'. */ |
/* before device registration, the only command allowed is 'register'. */ |
/* |
/* |
if (!fusd_dev->handle && msg->cmd != FUSD_REGISTER_DEVICE) { |
if (!fusd_dev->handle && msg->cmd != FUSD_REGISTER_DEVICE) { |
RDEBUG(2, "got a message other than 'register' on a new device!"); |
RDEBUG(2, "got a message other than 'register' on a new device!"); |
retval = -EINVAL; |
retval = -EINVAL; |
goto out; |
goto out; |
} |
} |
*/ |
*/ |
|
|
/* now dispatch the command to the appropriate handler */ |
/* now dispatch the command to the appropriate handler */ |
switch (msg->cmd) { |
switch (msg->cmd) { |
case FUSD_REGISTER_DEVICE: |
case FUSD_REGISTER_DEVICE: |
retval = fusd_register_device(fusd_dev, msg->parm.register_msg); |
retval = fusd_register_device(fusd_dev, msg->parm.register_msg); |
goto out; |
goto out; |
break; |
break; |
case FUSD_FOPS_REPLY: |
case FUSD_FOPS_REPLY: |
/* if reply is successful, DO NOT free the message */ |
/* if reply is successful, DO NOT free the message */ |
if ((retval = fusd_fops_reply(fusd_dev, msg)) == 0) { |
if ((retval = fusd_fops_reply(fusd_dev, msg)) == 0) { |
yield = 1; |
yield = 1; |
goto out_no_free; |
goto out_no_free; |
} |
} |
break; |
break; |
case FUSD_FOPS_NONBLOCK_REPLY: |
case FUSD_FOPS_NONBLOCK_REPLY: |
switch (msg->subcmd) { |
switch (msg->subcmd) { |
case FUSD_POLL_DIFF: |
case FUSD_POLL_DIFF: |
retval = fusd_polldiff_reply(fusd_dev, msg); |
retval = fusd_polldiff_reply(fusd_dev, msg); |
break; |
break; |
default: |
default: |
RDEBUG(2, "fusd_fops_nonblock got unknown subcmd %d", msg->subcmd); |
RDEBUG(2, "fusd_fops_nonblock got unknown subcmd %d", msg->subcmd); |
retval = -EINVAL; |
retval = -EINVAL; |
} |
} |
break; |
break; |
default: |
default: |
RDEBUG(2, "warning: unknown message type of %d received!", msg->cmd); |
RDEBUG(2, "warning: unknown message type of %d received!", msg->cmd); |
retval = -EINVAL; |
retval = -EINVAL; |
goto out; |
goto out; |
break; |
break; |
} |
} |
| |
| |
out: | out: |
if (msg && msg->data) { |
if (msg && msg->data) { |
VFREE(msg->data); |
VFREE(msg->data); |
msg->data = NULL; |
msg->data = NULL; |
} |
} |
if (msg != NULL) { |
if (msg != NULL) { |
KFREE(msg); |
KFREE(msg); |
msg = NULL; |
msg = NULL; |
} |
} |
| |
out_no_free: | out_no_free: |
| |
/* the functions we call indicate success by returning 0. we |
/* the functions we call indicate success by returning 0. we |
* convert that into a success indication by changing the retval to |
* convert that into a success indication by changing the retval to |
* the length of the write. */ |
* the length of the write. */ |
if (retval == 0) |
if (retval == 0) |
retval = user_data_len + user_msg_len; |
retval = user_data_len + user_msg_len; |
|
|
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
|
|
/* if we successfully completed someone's syscall, yield the |
/* if we successfully completed someone's syscall, yield the |
* processor to them immediately as a throughput optimization. we |
* processor to them immediately as a throughput optimization. we |
* also hope that in the case of bulk data transfer, their next |
* also hope that in the case of bulk data transfer, their next |
* syscall will come in before we are scheduled again. */ |
* syscall will come in before we are scheduled again. */ |
if (yield) { |
if (yield) { |
#ifdef SCHED_YIELD | #ifdef SCHED_YIELD |
current->policy |= SCHED_YIELD; |
current->policy |= SCHED_YIELD; |
#endif | #endif |
schedule(); |
schedule(); |
} |
} |
| |
return retval; |
return retval; |
| |
zombie_dev: | zombie_dev: |
invalid_dev: | invalid_dev: |
RDEBUG(1, "fusd_process_write: got invalid device!"); |
RDEBUG(1, "fusd_process_write: got invalid device!"); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
STATIC ssize_t fusd_write(struct file *file, | STATIC ssize_t fusd_write(struct file *file, |
const char *buffer, |
const char *buffer, |
size_t length, |
size_t length, |
loff_t *offset) |
loff_t *offset) |
{ | { |
return fusd_process_write(file, buffer, length, NULL, 0); |
return fusd_process_write(file, buffer, length, NULL, 0); |
} | } |
| |
| |
|
#if 0 |
STATIC ssize_t fusd_writev(struct file *file, | STATIC ssize_t fusd_writev(struct file *file, |
const struct iovec *iov, |
const struct iovec *iov, |
unsigned long count, |
unsigned long count, |
loff_t *offset) |
loff_t *offset) |
{ |
#else |
if (count != 2) { |
STATIC ssize_t fusd_aio_write(struct kiocb *iocb, |
RDEBUG(2, "fusd_writev: got illegal iov count of %ld", count); |
const struct iovec *iov, |
return -EINVAL; |
unsigned long count, |
} |
loff_t pos) |
|
#endif |
return fusd_process_write(file, |
{ |
iov[0].iov_base, iov[0].iov_len, |
if (count != 2) { |
iov[1].iov_base, iov[1].iov_len); |
RDEBUG(2, "fusd_writev: got illegal iov count of %ld", count); |
|
return -EINVAL; |
|
} |
|
|
|
return fusd_process_write(iocb->ki_filp, |
|
iov[0].iov_base, iov[0].iov_len, |
|
iov[1].iov_base, iov[1].iov_len); |
} | } |
| |
| |
|
|
* waiting to go from kernel to userspace. | * waiting to go from kernel to userspace. |
* | * |
* Important note: there are 2 possible read modes; | * Important note: there are 2 possible read modes; |
* 1) header-read mode; just the fusd_msg structure is returned. |
* 1) header-read mode; just the fusd_msg structure is returned. |
* | * |
* 2) data-read mode; the data portion of a call (NOT including the |
* 2) data-read mode; the data portion of a call (NOT including the |
* fusd_msg structure) is returned. |
* fusd_msg structure) is returned. |
* | * |
* The protocol this function expects the user-space library to follow | * The protocol this function expects the user-space library to follow |
* is: | * is: |
* 1) Userspace library reads header. |
* 1) Userspace library reads header. |
* 2) If fusd_msg->datalen == 0, goto step 4. |
* 2) If fusd_msg->datalen == 0, goto step 4. |
* 3) Userspace library reads data. |
* 3) Userspace library reads data. |
* 4) Message gets dequeued by the kernel. |
* 4) Message gets dequeued by the kernel. |
* | * |
* In other words, userspace first reads the header. Then, if and |
* In other words, userspace first reads the header. Then, if and |
* only if the header you read indicates that data follows, userspace | * only if the header you read indicates that data follows, userspace |
* follows with a read for that data. | * follows with a read for that data. |
* | * |
* For the header read, the length requested MUST be the exact length | * For the header read, the length requested MUST be the exact length |
* sizeof(fusd_msg_t). The corresponding data read must request |
* sizeof(fusd_msg_t). The corresponding data read must request |
* exactly the number of bytes in the data portion of the message. NO |
* exactly the number of bytes in the data portion of the message. NO |
* OTHER READ LENGTHS ARE ALLOWED - ALL OTHER READ LENGTHS WILL GET AN | * OTHER READ LENGTHS ARE ALLOWED - ALL OTHER READ LENGTHS WILL GET AN |
* -EINVAL. This is done as a basic safety measure to make sure we're |
* -EINVAL. This is done as a basic safety measure to make sure we're |
* talking to a userspace library that understands our protocol, and | * talking to a userspace library that understands our protocol, and |
* to detect framing errors. | * to detect framing errors. |
* | * |
* (note: normally you'd have to worry about reentrancy in a function | * (note: normally you'd have to worry about reentrancy in a function |
* like this because the process can block on the userspace access and | * like this because the process can block on the userspace access and |
* another might try to read. usually we would copy the message into |
* another might try to read. usually we would copy the message into |
* a temp location to make sure two processes don't get the same | * a temp location to make sure two processes don't get the same |
* message. however in this very specialized case, we're okay, |
* message. however in this very specialized case, we're okay, |
* because each instance of /dev/fusd has a completely independent | * because each instance of /dev/fusd has a completely independent |
* message queue.) */ |
* message queue.) */ |
| |
| |
/* do a "header" read: used by fusd_read */ | /* do a "header" read: used by fusd_read */ |
STATIC int fusd_read_header(char *user_buffer, size_t user_length, fusd_msg_t *msg) | STATIC int fusd_read_header(char *user_buffer, size_t user_length, fusd_msg_t *msg) |
{ | { |
int len = sizeof(fusd_msg_t); |
int len = sizeof(fusd_msg_t); |
| |
if (user_length != len) { |
if (user_length != len) { |
RDEBUG(4, "bad length of %d sent to /dev/fusd for peek", (int) user_length); |
RDEBUG(4, "bad length of %d sent to /dev/fusd for peek", (int) user_length); |
return -EINVAL; |
return -EINVAL; |
} |
} |
| |
if (copy_to_user(user_buffer, msg, len)) |
if (copy_to_user(user_buffer, msg, len)) |
return -EFAULT; |
return -EFAULT; |
| |
return sizeof(fusd_msg_t); |
return sizeof(fusd_msg_t); |
} | } |
| |
| |
/* do a "data" read: used by fusd_read */ | /* do a "data" read: used by fusd_read */ |
STATIC int fusd_read_data(char *user_buffer, size_t user_length, fusd_msg_t *msg) | STATIC int fusd_read_data(char *user_buffer, size_t user_length, fusd_msg_t *msg) |
{ | { |
int len = msg->datalen; |
int len = msg->datalen; |
| |
if (len == 0 || msg->data == NULL) { |
if (len == 0 || msg->data == NULL) { |
RDEBUG(1, "fusd_read_data: no data to send!"); |
RDEBUG(1, "fusd_read_data: no data to send!"); |
return -EIO; |
return -EIO; |
} |
} |
|
|
/* make sure the user is requesting exactly the right amount (as a |
/* make sure the user is requesting exactly the right amount (as a |
sanity check) */ |
sanity check) */ |
if (user_length != len) { |
if (user_length != len) { |
RDEBUG(4, "bad read for %d bytes on /dev/fusd (need %d)", (int) user_length,len); |
RDEBUG(4, "bad read for %d bytes on /dev/fusd (need %d)", (int) user_length,len); |
return -EINVAL; |
return -EINVAL; |
} |
} |
|
|
/* now copy to userspace */ |
/* now copy to userspace */ |
if (copy_to_user(user_buffer, msg->data, len)) |
if (copy_to_user(user_buffer, msg->data, len)) |
return -EFAULT; |
return -EFAULT; |
| |
/* done! */ |
/* done! */ |
return len; |
return len; |
} | } |
| |
| |
STATIC ssize_t fusd_read(struct file *file, | STATIC ssize_t fusd_read(struct file *file, |
char *user_buffer, /* The buffer to fill with data */ |
char *user_buffer, /* The buffer to fill with data */ |
size_t user_length, /* The length of the buffer */ |
size_t user_length, /* The length of the buffer */ |
loff_t *offset) /* Our offset in the file */ |
loff_t *offset) /* Our offset in the file */ |
{ |
{ |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
fusd_msgC_t *msg_out; |
fusd_msgC_t *msg_out; |
int retval, dequeue = 0; |
int retval, dequeue = 0; |
|
|
GET_FUSD_DEV(file->private_data, fusd_dev); |
GET_FUSD_DEV(file->private_data, fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
|
|
RDEBUG(15, "driver pid %d (/dev/%s) entering fusd_read", current->pid, |
RDEBUG(15, "driver pid %d (/dev/%s) entering fusd_read", current->pid, |
NAME(fusd_dev)); |
NAME(fusd_dev)); |
|
|
/* if no messages are waiting, either block or return EAGAIN */ |
/* if no messages are waiting, either block or return EAGAIN */ |
while ((msg_out = fusd_dev->msg_head) == NULL) { |
while ((msg_out = fusd_dev->msg_head) == NULL) { |
DECLARE_WAITQUEUE(wait, current); |
DECLARE_WAITQUEUE(wait, current); |
|
|
if (file->f_flags & O_NONBLOCK) { |
if (file->f_flags & O_NONBLOCK) { |
retval = -EAGAIN; |
retval = -EAGAIN; |
goto out; |
goto out; |
} |
} |
|
|
/* |
/* |
* sleep, waiting for a message to arrive. we are unrolling |
* sleep, waiting for a message to arrive. we are unrolling |
* interruptible_sleep_on to avoid a race between unlocking the |
* interruptible_sleep_on to avoid a race between unlocking the |
* device and sleeping (what if a message arrives in that |
* device and sleeping (what if a message arrives in that |
* interval?) |
* interval?) |
*/ |
*/ |
current->state = TASK_INTERRUPTIBLE; |
current->state = TASK_INTERRUPTIBLE; |
add_wait_queue(&fusd_dev->dev_wait, &wait); |
add_wait_queue(&fusd_dev->dev_wait, &wait); |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
schedule(); |
schedule(); |
remove_wait_queue(&fusd_dev->dev_wait, &wait); |
remove_wait_queue(&fusd_dev->dev_wait, &wait); |
LOCK_FUSD_DEV(fusd_dev); |
LOCK_FUSD_DEV(fusd_dev); |
|
|
/* we're back awake! --see if a signal woke us up */ |
/* we're back awake! --see if a signal woke us up */ |
if (signal_pending(current)) { |
if (signal_pending(current)) { |
retval = -ERESTARTSYS; |
retval = -ERESTARTSYS; |
goto out; |
goto out; |
} |
} |
} |
} |
|
|
/* is this a header read or data read? */ |
/* is this a header read or data read? */ |
if (!msg_out->peeked) { |
if (!msg_out->peeked) { |
/* this is a header read (first read) */ |
/* this is a header read (first read) */ |
retval = fusd_read_header(user_buffer, user_length, &msg_out->fusd_msg); |
retval = fusd_read_header(user_buffer, user_length, &msg_out->fusd_msg); |
|
|
/* is there data? if so, make sure next read gets data. if not, |
/* is there data? if so, make sure next read gets data. if not, |
* make sure message is dequeued now.*/ |
* make sure message is dequeued now.*/ |
if (msg_out->fusd_msg.datalen) { |
if (msg_out->fusd_msg.datalen) { |
msg_out->peeked = 1; |
msg_out->peeked = 1; |
dequeue = 0; |
dequeue = 0; |
} else { |
} else { |
dequeue = 1; |
dequeue = 1; |
} |
} |
} else { |
} else { |
/* this is a data read (second read) */ |
/* this is a data read (second read) */ |
retval = fusd_read_data(user_buffer, user_length, &msg_out->fusd_msg); |
retval = fusd_read_data(user_buffer, user_length, &msg_out->fusd_msg); |
dequeue = 1; /* message should be dequeued */ |
dequeue = 1; /* message should be dequeued */ |
} |
} |
|
|
/* if this message is done, take it out of the outgoing queue */ |
/* if this message is done, take it out of the outgoing queue */ |
if (dequeue) { |
if (dequeue) { |
if (fusd_dev->msg_tail == fusd_dev->msg_head) |
if (fusd_dev->msg_tail == fusd_dev->msg_head) |
fusd_dev->msg_tail = fusd_dev->msg_head = NULL; |
fusd_dev->msg_tail = fusd_dev->msg_head = NULL; |
else |
else |
fusd_dev->msg_head = msg_out->next; |
fusd_dev->msg_head = msg_out->next; |
FREE_FUSD_MSGC(msg_out); |
FREE_FUSD_MSGC(msg_out); |
} |
} |
| |
out: | out: |
UNLOCK_FUSD_DEV(fusd_dev); |
UNLOCK_FUSD_DEV(fusd_dev); |
return retval; |
return retval; |
| |
zombie_dev: | zombie_dev: |
invalid_dev: | invalid_dev: |
RDEBUG(2, "got read on /dev/fusd for unknown device!"); |
RDEBUG(2, "got read on /dev/fusd for unknown device!"); |
return -EPIPE; |
return -EPIPE; |
} | } |
| |
| |
/* a poll on /dev/fusd itself (the control channel) */ | /* a poll on /dev/fusd itself (the control channel) */ |
STATIC unsigned int fusd_poll(struct file *file, poll_table *wait) | STATIC unsigned int fusd_poll(struct file *file, poll_table *wait) |
{ | { |
fusd_dev_t *fusd_dev; |
fusd_dev_t *fusd_dev; |
GET_FUSD_DEV(file->private_data, fusd_dev); |
GET_FUSD_DEV(file->private_data, fusd_dev); |
| |
poll_wait(file, &fusd_dev->dev_wait, wait); |
poll_wait(file, &fusd_dev->dev_wait, wait); |
| |
if (fusd_dev->msg_head != NULL) { |
if (fusd_dev->msg_head != NULL) { |
return POLLIN | POLLRDNORM; |
return POLLIN | POLLRDNORM; |
} |
} |
| |
invalid_dev: | invalid_dev: |
return 0; |
return 0; |
} | } |
| |
| |
STATIC struct file_operations fusd_fops = { | STATIC struct file_operations fusd_fops = { |
owner: THIS_MODULE, |
owner: THIS_MODULE, |
open: fusd_open, |
open: fusd_open, |
read: fusd_read, |
read: fusd_read, |
write: fusd_write, |
write: fusd_write, |
writev: fusd_writev, |
#if 0 |
release: fusd_release, |
writev: fusd_writev, |
poll: fusd_poll, |
#else |
|
aio_write:fusd_aio_write, |
|
#endif |
|
release: fusd_release, |
|
poll: fusd_poll, |
}; | }; |
|
|
| |
| |
/*************************************************************************/ | /*************************************************************************/ |
| |
typedef struct fusd_status_state { | typedef struct fusd_status_state { |
int binary_status; |
int binary_status; |
int need_new_status; |
int need_new_status; |
char *curr_status; |
char *curr_status; |
int curr_status_len; |
int curr_status_len; |
int last_version_seen; |
int last_version_seen; |
} fusd_statcontext_t; | } fusd_statcontext_t; |
| |
/* open() called on /dev/fusd/status */ | /* open() called on /dev/fusd/status */ |
STATIC int fusd_status_open(struct inode *inode, struct file *file) | STATIC int fusd_status_open(struct inode *inode, struct file *file) |
{ | { |
int error = 0; |
int error = 0; |
fusd_statcontext_t *fs; |
fusd_statcontext_t *fs; |
| |
//MOD_INC_USE_COUNT; |
//MOD_INC_USE_COUNT; |
| |
if ((fs = KMALLOC(sizeof(fusd_statcontext_t), GFP_KERNEL)) == NULL) { |
if ((fs = KMALLOC(sizeof(fusd_statcontext_t), GFP_KERNEL)) == NULL) { |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
RDEBUG(1, "yikes! kernel can't allocate memory"); |
error = -ENOMEM; |
error = -ENOMEM; |
goto out; |
goto out; |
} |
} |
|
|
memset(fs, 0, sizeof(fusd_statcontext_t)); |
memset(fs, 0, sizeof(fusd_statcontext_t)); |
fs->need_new_status = 1; |
fs->need_new_status = 1; |
file->private_data = (void *) fs; |
file->private_data = (void *) fs; |
| |
out: | out: |
//if (error) |
//if (error) |
// MOD_DEC_USE_COUNT; |
// MOD_DEC_USE_COUNT; |
return error; |
return error; |
} | } |
| |
/* close on /dev/fusd_status */ | /* close on /dev/fusd_status */ |
STATIC int fusd_status_release(struct inode *inode, struct file *file) | STATIC int fusd_status_release(struct inode *inode, struct file *file) |
{ | { |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
| |
if (fs) { |
if (fs) { |
if (fs->curr_status) |
if (fs->curr_status) |
KFREE(fs->curr_status); |
KFREE(fs->curr_status); |
KFREE(fs); |
KFREE(fs); |
} |
} |
| |
//MOD_DEC_USE_COUNT; |
//MOD_DEC_USE_COUNT; |
return 0; |
return 0; |
} | } |
| |
| |
/* ioctl() on /dev/fusd/status */ | /* ioctl() on /dev/fusd/status */ |
STATIC int fusd_status_ioctl(struct inode *inode, struct file *file, | STATIC int fusd_status_ioctl(struct inode *inode, struct file *file, |
unsigned int cmd, unsigned long arg) |
unsigned int cmd, unsigned long arg) |
{ | { |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
| |
if (!fs) |
if (!fs) |
return -EIO; |
return -EIO; |
| |
switch (cmd) { |
switch (cmd) { |
case FUSD_STATUS_USE_BINARY: |
case FUSD_STATUS_USE_BINARY: |
fs->binary_status = 1; |
fs->binary_status = 1; |
return 0; |
return 0; |
default: |
default: |
return -EINVAL; |
return -EINVAL; |
break; |
break; |
} |
} |
} | } |
| |
| |
/* | /* |
* maybe_expand_buffer: expand a buffer exponentially as it fills. We |
* maybe_expand_buffer: expand a buffer exponentially as it fills. We |
* are given: | * are given: |
* | * |
* - A reference to a pointer to a buffer (buf) | * - A reference to a pointer to a buffer (buf) |
|
|
* - The amount of space we want to ensure is free in the buffer (space_needed) | * - The amount of space we want to ensure is free in the buffer (space_needed) |
* | * |
* If there isn't at least space_needed difference between buf_size | * If there isn't at least space_needed difference between buf_size |
* and len, the existing contents are moved into a larger buffer. |
* and len, the existing contents are moved into a larger buffer. |
*/ | */ |
STATIC int maybe_expand_buffer(char **buf, int *buf_size, int len, | STATIC int maybe_expand_buffer(char **buf, int *buf_size, int len, |
int space_needed) |
int space_needed) |
{ | { |
if (*buf_size - len < space_needed) { |
if (*buf_size - len < space_needed) { |
char *old_buf = *buf; |
char *old_buf = *buf; |
| |
*buf_size *= 2; |
*buf_size *= 2; |
*buf = KMALLOC(*buf_size, GFP_KERNEL); |
*buf = KMALLOC(*buf_size, GFP_KERNEL); |
| |
if (*buf != NULL) |
if (*buf != NULL) |
memmove(*buf, old_buf, len); |
memmove(*buf, old_buf, len); |
KFREE(old_buf); |
KFREE(old_buf); |
if (*buf == NULL) { |
if (*buf == NULL) { |
RDEBUG(1, "out of memory!"); |
RDEBUG(1, "out of memory!"); |
return -1; |
return -1; |
} |
} |
} |
} |
return 0; |
return 0; |
} | } |
| |
| |
|
|
/* Build a text buffer containing current fusd status. */ | /* Build a text buffer containing current fusd status. */ |
STATIC void fusd_status_build_text(fusd_statcontext_t *fs) | STATIC void fusd_status_build_text(fusd_statcontext_t *fs) |
{ | { |
int buf_size = 512; |
int buf_size = 512; |
char *buf = KMALLOC(buf_size, GFP_KERNEL); |
char *buf = KMALLOC(buf_size, GFP_KERNEL); |
int len = 0, total_clients = 0, total_files = 0; |
int len = 0, total_clients = 0, total_files = 0; |
struct list_head *tmp; |
struct list_head *tmp; |
|
|
if (buf == NULL) { |
if (buf == NULL) { |
RDEBUG(1, "fusd_status_build: out of memory!"); |
RDEBUG(1, "fusd_status_build: out of memory!"); |
return; |
return; |
} |
} |
|
|
len += snprintf(buf + len, buf_size - len, |
len += snprintf(buf + len, buf_size - len, |
" PID Open Name\n" |
" PID Open Name\n" |
"------ ---- -----------------\n"); |
"------ ---- -----------------\n"); |
|
|
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
list_for_each(tmp, &fusd_devlist_head) { |
list_for_each(tmp, &fusd_devlist_head) { |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
|
|
if (!d) |
if (!d) |
continue; |
continue; |
|
|
/* Possibly expand the buffer if we need more space */ |
/* Possibly expand the buffer if we need more space */ |
if (maybe_expand_buffer(&buf, &buf_size, len, FUSD_MAX_NAME_LENGTH+120) < 0) |
if (maybe_expand_buffer(&buf, &buf_size, len, FUSD_MAX_NAME_LENGTH+120) < 0) |
goto out; |
goto out; |
|
|
len += snprintf(buf + len, buf_size - len, |
len += snprintf(buf + len, buf_size - len, |
"%6d %4d %s%s\n", d->pid, d->num_files, |
"%6d %4d %s%s\n", d->pid, d->num_files, |
d->zombie ? "<zombie>" : "", NAME(d)); |
d->zombie ? "<zombie>" : "", NAME(d)); |
|
|
total_files++; |
total_files++; |
total_clients += d->num_files; |
total_clients += d->num_files; |
} |
} |
|
|
len += snprintf(buf + len, buf_size - len, |
len += snprintf(buf + len, buf_size - len, |
"\nFUSD $Revision: 1.97-kor-hacked-11 $ - %d devices used by %d clients\n", |
"\nFUSD $Revision: 1.97-kor-hacked-11 $ - %d devices used by %d clients\n", |
total_files, total_clients); |
total_files, total_clients); |
| |
out: | out: |
fs->last_version_seen = last_version; |
fs->last_version_seen = last_version; |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
| |
if (fs->curr_status) |
if (fs->curr_status) |
KFREE(fs->curr_status); |
KFREE(fs->curr_status); |
| |
fs->curr_status = buf; |
fs->curr_status = buf; |
fs->curr_status_len = len; |
fs->curr_status_len = len; |
fs->need_new_status = 0; |
fs->need_new_status = 0; |
} | } |
| |
| |
/* Build the binary version of status */ | /* Build the binary version of status */ |
STATIC void fusd_status_build_binary(fusd_statcontext_t *fs) | STATIC void fusd_status_build_binary(fusd_statcontext_t *fs) |
{ | { |
int buf_size = 512; |
int buf_size = 512; |
char *buf = KMALLOC(buf_size, GFP_KERNEL); |
char *buf = KMALLOC(buf_size, GFP_KERNEL); |
int len = 0, i = 0; |
int len = 0, i = 0; |
struct list_head *tmp; |
struct list_head *tmp; |
fusd_status_t *s; |
fusd_status_t *s; |
|
|
if (buf == NULL) { |
if (buf == NULL) { |
RDEBUG(1, "out of memory!"); |
RDEBUG(1, "out of memory!"); |
return; |
return; |
} |
} |
|
|
down(&fusd_devlist_sem); |
down(&fusd_devlist_sem); |
list_for_each(tmp, &fusd_devlist_head) { |
list_for_each(tmp, &fusd_devlist_head) { |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
fusd_dev_t *d = list_entry(tmp, fusd_dev_t, devlist); |
|
|
if (!d) |
if (!d) |
continue; |
continue; |
|
|
/* Possibly expand the buffer if we need more space */ |
/* Possibly expand the buffer if we need more space */ |
if (maybe_expand_buffer(&buf, &buf_size, len, sizeof(fusd_status_t)) < 0) |
if (maybe_expand_buffer(&buf, &buf_size, len, sizeof(fusd_status_t)) < 0) |
goto out; |
goto out; |
|
|
s = &((fusd_status_t *) buf)[i]; |
s = &((fusd_status_t *) buf)[i]; |
|
|
/* construct this status entry */ |
/* construct this status entry */ |
memset(s, 0, sizeof(fusd_status_t)); |
memset(s, 0, sizeof(fusd_status_t)); |
strncpy(s->name, NAME(d), FUSD_MAX_NAME_LENGTH); |
strncpy(s->name, NAME(d), FUSD_MAX_NAME_LENGTH); |
s->zombie = d->zombie; |
s->zombie = d->zombie; |
s->pid = d->pid; |
s->pid = d->pid; |
s->num_open = d->num_files; |
s->num_open = d->num_files; |
|
|
i++; |
i++; |
len += sizeof(fusd_status_t); |
len += sizeof(fusd_status_t); |
} |
} |
|
|
out: | out: |
fs->last_version_seen = last_version; |
fs->last_version_seen = last_version; |
up(&fusd_devlist_sem); |
up(&fusd_devlist_sem); |
| |
if (fs->curr_status) |
if (fs->curr_status) |
KFREE(fs->curr_status); |
KFREE(fs->curr_status); |
| |
fs->curr_status = buf; |
fs->curr_status = buf; |
fs->curr_status_len = len; |
fs->curr_status_len = len; |
fs->need_new_status = 0; |
fs->need_new_status = 0; |
} | } |
| |
| |
| |
STATIC ssize_t fusd_status_read(struct file *file, | STATIC ssize_t fusd_status_read(struct file *file, |
char *user_buffer, /* The buffer to fill with data */ |
char *user_buffer, /* The buffer to fill with data */ |
size_t user_length, /* The length of the buffer */ |
size_t user_length, /* The length of the buffer */ |
loff_t *offset) /* Our offset in the file */ |
loff_t *offset) /* Our offset in the file */ |
{ |
{ |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
|
|
if (!fs) |
if (!fs) |
return -EIO; |
return -EIO; |
|
|
/* create a new status page, if we aren't in the middle of one */ |
/* create a new status page, if we aren't in the middle of one */ |
if (fs->need_new_status) { |
if (fs->need_new_status) { |
if (fs->binary_status) |
if (fs->binary_status) |
fusd_status_build_binary(fs); |
fusd_status_build_binary(fs); |
else |
else |
fusd_status_build_text(fs); |
fusd_status_build_text(fs); |
} |
} |
|
|
/* return EOF if we're at the end */ |
/* return EOF if we're at the end */ |
if (fs->curr_status == NULL || fs->curr_status_len == 0) { |
if (fs->curr_status == NULL || fs->curr_status_len == 0) { |
fs->need_new_status = 1; |
fs->need_new_status = 1; |
return 0; |
return 0; |
} |
} |
|
|
/* return only as much data as we have */ |
/* return only as much data as we have */ |
if (fs->curr_status_len < user_length) |
if (fs->curr_status_len < user_length) |
user_length = fs->curr_status_len; |
user_length = fs->curr_status_len; |
if (copy_to_user(user_buffer, fs->curr_status, user_length)) |
if (copy_to_user(user_buffer, fs->curr_status, user_length)) |
return -EFAULT; |
return -EFAULT; |
|
|
/* update fs, so we don't return the same data next time */ |
/* update fs, so we don't return the same data next time */ |
fs->curr_status_len -= user_length; |
fs->curr_status_len -= user_length; |
if (fs->curr_status_len) |
if (fs->curr_status_len) |
memmove(fs->curr_status, fs->curr_status + user_length, fs->curr_status_len); |
memmove(fs->curr_status, fs->curr_status + user_length, fs->curr_status_len); |
else { |
else { |
KFREE(fs->curr_status); |
KFREE(fs->curr_status); |
fs->curr_status = NULL; |
fs->curr_status = NULL; |
} |
} |
| |
return user_length; |
return user_length; |
} | } |
| |
| |
/* a poll on /dev/fusd itself (the control channel) */ | /* a poll on /dev/fusd itself (the control channel) */ |
STATIC unsigned int fusd_status_poll(struct file *file, poll_table *wait) | STATIC unsigned int fusd_status_poll(struct file *file, poll_table *wait) |
{ | { |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
fusd_statcontext_t *fs = (fusd_statcontext_t *) file->private_data; |
| |
poll_wait(file, &new_device_wait, wait); |
poll_wait(file, &new_device_wait, wait); |
| |
if (fs->last_version_seen < last_version) |
if (fs->last_version_seen < last_version) |
return POLLIN | POLLRDNORM; |
return POLLIN | POLLRDNORM; |
else |
else |
return 0; |
return 0; |
} | } |
| |
| |
STATIC struct file_operations fusd_status_fops = { | STATIC struct file_operations fusd_status_fops = { |
owner: THIS_MODULE, |
owner: THIS_MODULE, |
open: fusd_status_open, |
open: fusd_status_open, |
ioctl: fusd_status_ioctl, |
ioctl: fusd_status_ioctl, |
read: fusd_status_read, |
read: fusd_status_read, |
release: fusd_status_release, |
release: fusd_status_release, |
poll: fusd_status_poll, |
poll: fusd_status_poll, |
}; | }; |
|
|
| |
/*************************************************************************/ | /*************************************************************************/ |
| |
| |
STATIC int init_fusd(void) | STATIC int init_fusd(void) |
{ | { |
int retval; |
int retval; |
| |
#ifdef CONFIG_FUSD_MEMDEBUG | #ifdef CONFIG_FUSD_MEMDEBUG |
if ((retval = fusd_mem_init()) < 0) |
if ((retval = fusd_mem_init()) < 0) |
return retval; |
return retval; |
#endif | #endif |
| |
| |
printk(KERN_INFO |
printk(KERN_INFO |
"fusd: starting, $Revision: 1.97-kor-hacked-11 $, $Date: 2003/07/11 22:29:39 $"); |
"fusd: starting, $Revision: 1.97-kor-hacked-11 $, $Date: 2003/07/11 22:29:39 $"); |
#ifdef CVSTAG | #ifdef CVSTAG |
printk(", release %s", CVSTAG); |
printk(", release %s", CVSTAG); |
#endif | #endif |
#ifdef CONFIG_FUSD_DEBUG | #ifdef CONFIG_FUSD_DEBUG |
printk(", debuglevel=%d\n", fusd_debug_level); |
printk(", debuglevel=%d\n", fusd_debug_level); |
#else | #else |
printk(", debugging messages disabled\n"); |
printk(", debugging messages disabled\n"); |
#endif | #endif |
| |
fusd_control_device = NULL; |
fusd_control_device = NULL; |
fusd_status_device = NULL; |
fusd_status_device = NULL; |
|
|
fusd_class = class_create(THIS_MODULE, "fusd"); |
fusd_class = class_create(THIS_MODULE, "fusd"); |
if(IS_ERR(fusd_class)) |
if(IS_ERR(fusd_class)) |
{ |
{ |
retval = PTR_ERR(fusd_class); |
retval = PTR_ERR(fusd_class); |
printk("class_create failed status: %d\n", retval); |
printk("class_create failed status: %d\n", retval); |
goto fail0; |
goto fail0; |
} |
} |
|
|
control_id = 0; |
control_id = 0; |
|
|
if((retval = alloc_chrdev_region(&control_id, 0, 1, FUSD_CONTROL_FILENAME)) < 0) |
if((retval = alloc_chrdev_region(&control_id, 0, 1, FUSD_CONTROL_FILENAME)) < 0) |
{ |
{ |
printk("alloc_chrdev_region failed status: %d\n", retval); |
printk("alloc_chrdev_region failed status: %d\n", retval); |
goto fail1; |
goto fail1; |
} |
} |
#ifdef CONFIG_DEVFS_FS | #ifdef CONFIG_DEVFS_FS |
if((retval = devfs_mk_cdev(control_id, S_IFCHR | 0666, FUSD_CONTROL_FILENAME)) < 0) |
if((retval = devfs_mk_cdev(control_id, S_IFCHR | 0666, FUSD_CONTROL_FILENAME)) < 0) |
{ |
{ |
printk("devfs_mk_cdev failed status: %d\n", retval); |
printk("devfs_mk_cdev failed status: %d\n", retval); |
goto fail2; |
goto fail2; |
} |
} |
#endif |
#endif |
|
|
fusd_control_device = cdev_alloc(); |
fusd_control_device = cdev_alloc(); |
if(fusd_control_device == NULL) |
if(fusd_control_device == NULL) |
{ |
{ |
retval = -ENOMEM; |
retval = -ENOMEM; |
goto fail3; |
goto fail3; |
} |
} |
|
|
fusd_control_device->owner = THIS_MODULE; |
fusd_control_device->owner = THIS_MODULE; |
fusd_control_device->ops = &fusd_fops; |
fusd_control_device->ops = &fusd_fops; |
kobject_set_name(&fusd_control_device->kobj, FUSD_CONTROL_FILENAME); |
kobject_set_name(&fusd_control_device->kobj, FUSD_CONTROL_FILENAME); |
|
|
printk("cdev control id: %d\n", control_id); |
printk("cdev control id: %d\n", control_id); |
if((retval = cdev_add(fusd_control_device, control_id, 1)) < 0) |
if((retval = cdev_add(fusd_control_device, control_id, 1)) < 0) |
{ |
{ |
printk("cdev_add failed status: %d\n", retval); |
printk("cdev_add failed status: %d\n", retval); |
kobject_put(&fusd_control_device->kobj); |
kobject_put(&fusd_control_device->kobj); |
goto fail4; |
goto fail4; |
} |
} |
|
|
fusd_control_class_device = CLASS_DEVICE_CREATE(fusd_class, NULL, control_id, NULL, "control"); |
fusd_control_class_device = CLASS_DEVICE_CREATE(fusd_class, NULL, control_id, NULL, "control"); |
if(fusd_control_class_device == NULL) |
if(fusd_control_class_device == NULL) |
{ |
{ |
retval = PTR_ERR(fusd_control_class_device); |
retval = PTR_ERR(fusd_control_class_device); |
printk("class_device_create failed status: %d\n", retval); |
printk("class_device_create failed status: %d\n", retval); |
goto fail5; |
goto fail5; |
} |
} |
|
|
status_id = 0; |
status_id = 0; |
|
|
if((retval = alloc_chrdev_region(&status_id, 0, 1, FUSD_STATUS_FILENAME)) < 0) |
if((retval = alloc_chrdev_region(&status_id, 0, 1, FUSD_STATUS_FILENAME)) < 0) |
{ |
{ |
printk("alloc_chrdev_region failed status: %d\n", retval); |
printk("alloc_chrdev_region failed status: %d\n", retval); |
goto fail6; |
goto fail6; |
} |
} |
#ifdef CONFIG_DEVFS_FS | #ifdef CONFIG_DEVFS_FS |
if((retval = devfs_mk_cdev(status_id, S_IFCHR | 0666, FUSD_STATUS_FILENAME)) < 0) |
if((retval = devfs_mk_cdev(status_id, S_IFCHR | 0666, FUSD_STATUS_FILENAME)) < 0) |
{ |
{ |
printk("devfs_mk_cdev failed status: %d\n", retval); |
printk("devfs_mk_cdev failed status: %d\n", retval); |
goto fail7; |
goto fail7; |
} |
} |
#endif |
#endif |
|
|
fusd_status_device = cdev_alloc(); |
fusd_status_device = cdev_alloc(); |
if(fusd_status_device == NULL) |
if(fusd_status_device == NULL) |
{ |
{ |
retval = -ENOMEM; |
retval = -ENOMEM; |
goto fail8; |
goto fail8; |
} |
} |
|
|
fusd_status_device->owner = THIS_MODULE; |
fusd_status_device->owner = THIS_MODULE; |
fusd_status_device->ops = &fusd_status_fops; |
fusd_status_device->ops = &fusd_status_fops; |
kobject_set_name(&fusd_status_device->kobj, FUSD_STATUS_FILENAME); |
kobject_set_name(&fusd_status_device->kobj, FUSD_STATUS_FILENAME); |
|
|
if((retval = cdev_add(fusd_status_device, status_id, 1)) < 0) |
if((retval = cdev_add(fusd_status_device, status_id, 1)) < 0) |
{ |
{ |
printk("cdev_add failed status: %d\n", retval); |
printk("cdev_add failed status: %d\n", retval); |
kobject_put(&fusd_status_device->kobj); |
kobject_put(&fusd_status_device->kobj); |
goto fail9; |
goto fail9; |
} |
} |
|
|
fusd_status_class_device = CLASS_DEVICE_CREATE(fusd_class, NULL, status_id, NULL, "status"); |
fusd_status_class_device = CLASS_DEVICE_CREATE(fusd_class, NULL, status_id, NULL, "status"); |
if(fusd_status_class_device == NULL) |
if(fusd_status_class_device == NULL) |
{ |
{ |
printk("class_device_create failed status: %d\n", retval); |
printk("class_device_create failed status: %d\n", retval); |
retval = PTR_ERR(fusd_status_class_device); |
retval = PTR_ERR(fusd_status_class_device); |
goto fail10; |
goto fail10; |
} |
} |
|
|
RDEBUG(1, "registration successful"); |
RDEBUG(1, "registration successful"); |
return 0; |
return 0; |
| |
fail10: | fail10: |
cdev_del(fusd_status_device); |
cdev_del(fusd_status_device); |
fail9: | fail9: |
kfree(fusd_status_device); |
kfree(fusd_status_device); |
fail8: | fail8: |
#ifdef CONFIG_DEVFS_FS | #ifdef CONFIG_DEVFS_FS |
devfs_remove(FUSD_STATUS_FILENAME); |
devfs_remove(FUSD_STATUS_FILENAME); |
#endif | #endif |
fail7: |
/*fail7:*/ |
unregister_chrdev_region(status_id, 1); |
unregister_chrdev_region(status_id, 1); |
fail6: | fail6: |
class_device_destroy(fusd_class, control_id); |
class_device_destroy(fusd_class, control_id); |
fail5: | fail5: |
cdev_del(fusd_control_device); |
cdev_del(fusd_control_device); |
fail4: | fail4: |
kfree(fusd_control_device); |
kfree(fusd_control_device); |
fail3: | fail3: |
#ifdef CONFIG_DEVFS_FS | #ifdef CONFIG_DEVFS_FS |
devfs_remove(FUSD_CONTROL_FILENAME); |
devfs_remove(FUSD_CONTROL_FILENAME); |
#endif | #endif |
fail2: |
/*fail2:*/ |
unregister_chrdev_region(control_id, 1); |
unregister_chrdev_region(control_id, 1); |
| |
fail1: | fail1: |
class_destroy(fusd_class); |
class_destroy(fusd_class); |
fail0: | fail0: |
return retval; |
return retval; |
} | } |
| |
STATIC void cleanup_fusd(void) | STATIC void cleanup_fusd(void) |
{ | { |
RDEBUG(1, "cleaning up"); |
RDEBUG(1, "cleaning up"); |
| |
class_device_destroy(fusd_class, status_id); |
class_device_destroy(fusd_class, status_id); |
class_device_destroy(fusd_class, control_id); |
class_device_destroy(fusd_class, control_id); |
|
|
cdev_del(fusd_control_device); |
|
cdev_del(fusd_status_device); |
|
| |
devfs_remove(FUSD_CONTROL_FILENAME); |
cdev_del(fusd_control_device); |
devfs_remove(FUSD_STATUS_FILENAME); |
cdev_del(fusd_status_device); |
| |
|
#ifdef CONFIG_DEVFS_FS |
|
devfs_remove(FUSD_CONTROL_FILENAME); |
|
devfs_remove(FUSD_STATUS_FILENAME); |
|
#endif |
|
|
|
class_destroy(fusd_class); |
| |
class_destroy(fusd_class); |
|
|
|
#ifdef CONFIG_FUSD_MEMDEBUG | #ifdef CONFIG_FUSD_MEMDEBUG |
fusd_mem_cleanup(); |
fusd_mem_cleanup(); |
#endif | #endif |
} | } |
| |
module_init(init_fusd); | module_init(init_fusd); |
module_exit(cleanup_fusd); | module_exit(cleanup_fusd); |
|
|