mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	[PATCH] relay: migrate from relayfs to a generic relay API
Original patch from Paul Mundt, sysfs parts removed by me since they were broken. Signed-off-by: Jens Axboe <axboe@suse.de>
This commit is contained in:
		
							parent
							
								
									b0e6e96299
								
							
						
					
					
						commit
						b86ff981a8
					
				
					 12 changed files with 1212 additions and 1290 deletions
				
			
		
							
								
								
									
										12
									
								
								fs/Kconfig
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								fs/Kconfig
									
									
									
									
									
								
							|  | @ -859,18 +859,6 @@ config RAMFS | |||
| 	  To compile this as a module, choose M here: the module will be called | ||||
| 	  ramfs. | ||||
| 
 | ||||
| config RELAYFS_FS | ||||
| 	tristate "Relayfs file system support" | ||||
| 	---help--- | ||||
| 	  Relayfs is a high-speed data relay filesystem designed to provide | ||||
| 	  an efficient mechanism for tools and facilities to relay large | ||||
| 	  amounts of data from kernel space to user space. | ||||
| 
 | ||||
| 	  To compile this code as a module, choose M here: the module will be | ||||
| 	  called relayfs. | ||||
| 
 | ||||
| 	  If unsure, say N. | ||||
| 
 | ||||
| config CONFIGFS_FS | ||||
| 	tristate "Userspace-driven configuration filesystem (EXPERIMENTAL)" | ||||
| 	depends on EXPERIMENTAL | ||||
|  |  | |||
|  | @ -91,7 +91,6 @@ obj-$(CONFIG_AUTOFS4_FS)	+= autofs4/ | |||
| obj-$(CONFIG_ADFS_FS)		+= adfs/ | ||||
| obj-$(CONFIG_FUSE_FS)		+= fuse/ | ||||
| obj-$(CONFIG_UDF_FS)		+= udf/ | ||||
| obj-$(CONFIG_RELAYFS_FS)	+= relayfs/ | ||||
| obj-$(CONFIG_SUN_OPENPROMFS)	+= openpromfs/ | ||||
| obj-$(CONFIG_JFS_FS)		+= jfs/ | ||||
| obj-$(CONFIG_XFS_FS)		+= xfs/ | ||||
|  |  | |||
|  | @ -1,4 +0,0 @@ | |||
| obj-$(CONFIG_RELAYFS_FS) += relayfs.o | ||||
| 
 | ||||
| relayfs-y := relay.o inode.o buffers.o | ||||
| 
 | ||||
|  | @ -1,190 +0,0 @@ | |||
| /*
 | ||||
|  * RelayFS buffer management code. | ||||
|  * | ||||
|  * Copyright (C) 2002-2005 - Tom Zanussi (zanussi@us.ibm.com), IBM Corp | ||||
|  * Copyright (C) 1999-2005 - Karim Yaghmour (karim@opersys.com) | ||||
|  * | ||||
|  * This file is released under the GPL. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/module.h> | ||||
| #include <linux/vmalloc.h> | ||||
| #include <linux/mm.h> | ||||
| #include <linux/relayfs_fs.h> | ||||
| #include "relay.h" | ||||
| #include "buffers.h" | ||||
| 
 | ||||
| /*
 | ||||
|  * close() vm_op implementation for relayfs file mapping. | ||||
|  */ | ||||
| static void relay_file_mmap_close(struct vm_area_struct *vma) | ||||
| { | ||||
| 	struct rchan_buf *buf = vma->vm_private_data; | ||||
| 	buf->chan->cb->buf_unmapped(buf, vma->vm_file); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * nopage() vm_op implementation for relayfs file mapping. | ||||
|  */ | ||||
| static struct page *relay_buf_nopage(struct vm_area_struct *vma, | ||||
| 				     unsigned long address, | ||||
| 				     int *type) | ||||
| { | ||||
| 	struct page *page; | ||||
| 	struct rchan_buf *buf = vma->vm_private_data; | ||||
| 	unsigned long offset = address - vma->vm_start; | ||||
| 
 | ||||
| 	if (address > vma->vm_end) | ||||
| 		return NOPAGE_SIGBUS; /* Disallow mremap */ | ||||
| 	if (!buf) | ||||
| 		return NOPAGE_OOM; | ||||
| 
 | ||||
| 	page = vmalloc_to_page(buf->start + offset); | ||||
| 	if (!page) | ||||
| 		return NOPAGE_OOM; | ||||
| 	get_page(page); | ||||
| 
 | ||||
| 	if (type) | ||||
| 		*type = VM_FAULT_MINOR; | ||||
| 
 | ||||
| 	return page; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * vm_ops for relay file mappings. | ||||
|  */ | ||||
| static struct vm_operations_struct relay_file_mmap_ops = { | ||||
| 	.nopage = relay_buf_nopage, | ||||
| 	.close = relay_file_mmap_close, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_mmap_buf: - mmap channel buffer to process address space | ||||
|  *	@buf: relay channel buffer | ||||
|  *	@vma: vm_area_struct describing memory to be mapped | ||||
|  * | ||||
|  *	Returns 0 if ok, negative on error | ||||
|  * | ||||
|  *	Caller should already have grabbed mmap_sem. | ||||
|  */ | ||||
| int relay_mmap_buf(struct rchan_buf *buf, struct vm_area_struct *vma) | ||||
| { | ||||
| 	unsigned long length = vma->vm_end - vma->vm_start; | ||||
| 	struct file *filp = vma->vm_file; | ||||
| 
 | ||||
| 	if (!buf) | ||||
| 		return -EBADF; | ||||
| 
 | ||||
| 	if (length != (unsigned long)buf->chan->alloc_size) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	vma->vm_ops = &relay_file_mmap_ops; | ||||
| 	vma->vm_private_data = buf; | ||||
| 	buf->chan->cb->buf_mapped(buf, filp); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_alloc_buf - allocate a channel buffer | ||||
|  *	@buf: the buffer struct | ||||
|  *	@size: total size of the buffer | ||||
|  * | ||||
|  *	Returns a pointer to the resulting buffer, NULL if unsuccessful | ||||
|  */ | ||||
| static void *relay_alloc_buf(struct rchan_buf *buf, unsigned long size) | ||||
| { | ||||
| 	void *mem; | ||||
| 	unsigned int i, j, n_pages; | ||||
| 
 | ||||
| 	size = PAGE_ALIGN(size); | ||||
| 	n_pages = size >> PAGE_SHIFT; | ||||
| 
 | ||||
| 	buf->page_array = kcalloc(n_pages, sizeof(struct page *), GFP_KERNEL); | ||||
| 	if (!buf->page_array) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	for (i = 0; i < n_pages; i++) { | ||||
| 		buf->page_array[i] = alloc_page(GFP_KERNEL); | ||||
| 		if (unlikely(!buf->page_array[i])) | ||||
| 			goto depopulate; | ||||
| 	} | ||||
| 	mem = vmap(buf->page_array, n_pages, VM_MAP, PAGE_KERNEL); | ||||
| 	if (!mem) | ||||
| 		goto depopulate; | ||||
| 
 | ||||
| 	memset(mem, 0, size); | ||||
| 	buf->page_count = n_pages; | ||||
| 	return mem; | ||||
| 
 | ||||
| depopulate: | ||||
| 	for (j = 0; j < i; j++) | ||||
| 		__free_page(buf->page_array[j]); | ||||
| 	kfree(buf->page_array); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_create_buf - allocate and initialize a channel buffer | ||||
|  *	@alloc_size: size of the buffer to allocate | ||||
|  *	@n_subbufs: number of sub-buffers in the channel | ||||
|  * | ||||
|  *	Returns channel buffer if successful, NULL otherwise | ||||
|  */ | ||||
| struct rchan_buf *relay_create_buf(struct rchan *chan) | ||||
| { | ||||
| 	struct rchan_buf *buf = kcalloc(1, sizeof(struct rchan_buf), GFP_KERNEL); | ||||
| 	if (!buf) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	buf->padding = kmalloc(chan->n_subbufs * sizeof(size_t *), GFP_KERNEL); | ||||
| 	if (!buf->padding) | ||||
| 		goto free_buf; | ||||
| 
 | ||||
| 	buf->start = relay_alloc_buf(buf, chan->alloc_size); | ||||
| 	if (!buf->start) | ||||
| 		goto free_buf; | ||||
| 
 | ||||
| 	buf->chan = chan; | ||||
| 	kref_get(&buf->chan->kref); | ||||
| 	return buf; | ||||
| 
 | ||||
| free_buf: | ||||
| 	kfree(buf->padding); | ||||
| 	kfree(buf); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_destroy_buf - destroy an rchan_buf struct and associated buffer | ||||
|  *	@buf: the buffer struct | ||||
|  */ | ||||
| void relay_destroy_buf(struct rchan_buf *buf) | ||||
| { | ||||
| 	struct rchan *chan = buf->chan; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	if (likely(buf->start)) { | ||||
| 		vunmap(buf->start); | ||||
| 		for (i = 0; i < buf->page_count; i++) | ||||
| 			__free_page(buf->page_array[i]); | ||||
| 		kfree(buf->page_array); | ||||
| 	} | ||||
| 	kfree(buf->padding); | ||||
| 	kfree(buf); | ||||
| 	kref_put(&chan->kref, relay_destroy_channel); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_remove_buf - remove a channel buffer | ||||
|  * | ||||
|  *	Removes the file from the relayfs fileystem, which also frees the | ||||
|  *	rchan_buf_struct and the channel buffer.  Should only be called from | ||||
|  *	kref_put(). | ||||
|  */ | ||||
| void relay_remove_buf(struct kref *kref) | ||||
| { | ||||
| 	struct rchan_buf *buf = container_of(kref, struct rchan_buf, kref); | ||||
| 	buf->chan->cb->remove_buf_file(buf->dentry); | ||||
| 	relay_destroy_buf(buf); | ||||
| } | ||||
|  | @ -1,12 +0,0 @@ | |||
| #ifndef _BUFFERS_H | ||||
| #define _BUFFERS_H | ||||
| 
 | ||||
| /* This inspired by rtai/shmem */ | ||||
| #define FIX_SIZE(x) (((x) - 1) & PAGE_MASK) + PAGE_SIZE | ||||
| 
 | ||||
| extern int relay_mmap_buf(struct rchan_buf *buf, struct vm_area_struct *vma); | ||||
| extern struct rchan_buf *relay_create_buf(struct rchan *chan); | ||||
| extern void relay_destroy_buf(struct rchan_buf *buf); | ||||
| extern void relay_remove_buf(struct kref *kref); | ||||
| 
 | ||||
| #endif/* _BUFFERS_H */ | ||||
|  | @ -1,581 +0,0 @@ | |||
| /*
 | ||||
|  * VFS-related code for RelayFS, a high-speed data relay filesystem. | ||||
|  * | ||||
|  * Copyright (C) 2003-2005 - Tom Zanussi <zanussi@us.ibm.com>, IBM Corp | ||||
|  * Copyright (C) 2003-2005 - Karim Yaghmour <karim@opersys.com> | ||||
|  * | ||||
|  * Based on ramfs, Copyright (C) 2002 - Linus Torvalds | ||||
|  * | ||||
|  * This file is released under the GPL. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/module.h> | ||||
| #include <linux/fs.h> | ||||
| #include <linux/mount.h> | ||||
| #include <linux/pagemap.h> | ||||
| #include <linux/init.h> | ||||
| #include <linux/string.h> | ||||
| #include <linux/backing-dev.h> | ||||
| #include <linux/namei.h> | ||||
| #include <linux/poll.h> | ||||
| #include <linux/relayfs_fs.h> | ||||
| #include "relay.h" | ||||
| #include "buffers.h" | ||||
| 
 | ||||
| #define RELAYFS_MAGIC			0xF0B4A981 | ||||
| 
 | ||||
| static struct vfsmount *		relayfs_mount; | ||||
| static int				relayfs_mount_count; | ||||
| 
 | ||||
| static struct backing_dev_info		relayfs_backing_dev_info = { | ||||
| 	.ra_pages	= 0,	/* No readahead */ | ||||
| 	.capabilities	= BDI_CAP_NO_ACCT_DIRTY | BDI_CAP_NO_WRITEBACK, | ||||
| }; | ||||
| 
 | ||||
| static struct inode *relayfs_get_inode(struct super_block *sb, | ||||
| 				       int mode, | ||||
|  				       struct file_operations *fops, | ||||
| 				       void *data) | ||||
| { | ||||
| 	struct inode *inode; | ||||
| 
 | ||||
| 	inode = new_inode(sb); | ||||
| 	if (!inode) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	inode->i_mode = mode; | ||||
| 	inode->i_uid = 0; | ||||
| 	inode->i_gid = 0; | ||||
| 	inode->i_blksize = PAGE_CACHE_SIZE; | ||||
| 	inode->i_blocks = 0; | ||||
| 	inode->i_mapping->backing_dev_info = &relayfs_backing_dev_info; | ||||
| 	inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; | ||||
| 	switch (mode & S_IFMT) { | ||||
| 	case S_IFREG: | ||||
| 		inode->i_fop = fops; | ||||
| 		if (data) | ||||
| 			inode->u.generic_ip = data; | ||||
| 		break; | ||||
| 	case S_IFDIR: | ||||
| 		inode->i_op = &simple_dir_inode_operations; | ||||
| 		inode->i_fop = &simple_dir_operations; | ||||
| 
 | ||||
| 		/* directory inodes start off with i_nlink == 2 (for "." entry) */ | ||||
| 		inode->i_nlink++; | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	return inode; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relayfs_create_entry - create a relayfs directory or file | ||||
|  *	@name: the name of the file to create | ||||
|  *	@parent: parent directory | ||||
|  *	@mode: mode | ||||
|  *	@fops: file operations to use for the file | ||||
|  *	@data: user-associated data for this file | ||||
|  * | ||||
|  *	Returns the new dentry, NULL on failure | ||||
|  * | ||||
|  *	Creates a file or directory with the specifed permissions. | ||||
|  */ | ||||
| static struct dentry *relayfs_create_entry(const char *name, | ||||
| 					   struct dentry *parent, | ||||
| 					   int mode, | ||||
| 					   struct file_operations *fops, | ||||
| 					   void *data) | ||||
| { | ||||
| 	struct dentry *d; | ||||
| 	struct inode *inode; | ||||
| 	int error = 0; | ||||
| 
 | ||||
| 	BUG_ON(!name || !(S_ISREG(mode) || S_ISDIR(mode))); | ||||
| 
 | ||||
| 	error = simple_pin_fs("relayfs", &relayfs_mount, &relayfs_mount_count); | ||||
| 	if (error) { | ||||
| 		printk(KERN_ERR "Couldn't mount relayfs: errcode %d\n", error); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!parent && relayfs_mount && relayfs_mount->mnt_sb) | ||||
| 		parent = relayfs_mount->mnt_sb->s_root; | ||||
| 
 | ||||
| 	if (!parent) { | ||||
| 		simple_release_fs(&relayfs_mount, &relayfs_mount_count); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	parent = dget(parent); | ||||
| 	mutex_lock(&parent->d_inode->i_mutex); | ||||
| 	d = lookup_one_len(name, parent, strlen(name)); | ||||
| 	if (IS_ERR(d)) { | ||||
| 		d = NULL; | ||||
| 		goto release_mount; | ||||
| 	} | ||||
| 
 | ||||
| 	if (d->d_inode) { | ||||
| 		d = NULL; | ||||
| 		goto release_mount; | ||||
| 	} | ||||
| 
 | ||||
| 	inode = relayfs_get_inode(parent->d_inode->i_sb, mode, fops, data); | ||||
| 	if (!inode) { | ||||
| 		d = NULL; | ||||
| 		goto release_mount; | ||||
| 	} | ||||
| 
 | ||||
| 	d_instantiate(d, inode); | ||||
| 	dget(d);	/* Extra count - pin the dentry in core */ | ||||
| 
 | ||||
| 	if (S_ISDIR(mode)) | ||||
| 		parent->d_inode->i_nlink++; | ||||
| 
 | ||||
| 	goto exit; | ||||
| 
 | ||||
| release_mount: | ||||
| 	simple_release_fs(&relayfs_mount, &relayfs_mount_count); | ||||
| 
 | ||||
| exit: | ||||
| 	mutex_unlock(&parent->d_inode->i_mutex); | ||||
| 	dput(parent); | ||||
| 	return d; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relayfs_create_file - create a file in the relay filesystem | ||||
|  *	@name: the name of the file to create | ||||
|  *	@parent: parent directory | ||||
|  *	@mode: mode, if not specied the default perms are used | ||||
|  *	@fops: file operations to use for the file | ||||
|  *	@data: user-associated data for this file | ||||
|  * | ||||
|  *	Returns file dentry if successful, NULL otherwise. | ||||
|  * | ||||
|  *	The file will be created user r on behalf of current user. | ||||
|  */ | ||||
| struct dentry *relayfs_create_file(const char *name, | ||||
| 				   struct dentry *parent, | ||||
| 				   int mode, | ||||
| 				   struct file_operations *fops, | ||||
| 				   void *data) | ||||
| { | ||||
| 	BUG_ON(!fops); | ||||
| 
 | ||||
| 	if (!mode) | ||||
| 		mode = S_IRUSR; | ||||
| 	mode = (mode & S_IALLUGO) | S_IFREG; | ||||
| 
 | ||||
| 	return relayfs_create_entry(name, parent, mode, fops, data); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relayfs_create_dir - create a directory in the relay filesystem | ||||
|  *	@name: the name of the directory to create | ||||
|  *	@parent: parent directory, NULL if parent should be fs root | ||||
|  * | ||||
|  *	Returns directory dentry if successful, NULL otherwise. | ||||
|  * | ||||
|  *	The directory will be created world rwx on behalf of current user. | ||||
|  */ | ||||
| struct dentry *relayfs_create_dir(const char *name, struct dentry *parent) | ||||
| { | ||||
| 	int mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; | ||||
| 	return relayfs_create_entry(name, parent, mode, NULL, NULL); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relayfs_remove - remove a file or directory in the relay filesystem | ||||
|  *	@dentry: file or directory dentry | ||||
|  * | ||||
|  *	Returns 0 if successful, negative otherwise. | ||||
|  */ | ||||
| int relayfs_remove(struct dentry *dentry) | ||||
| { | ||||
| 	struct dentry *parent; | ||||
| 	int error = 0; | ||||
| 
 | ||||
| 	if (!dentry) | ||||
| 		return -EINVAL; | ||||
| 	parent = dentry->d_parent; | ||||
| 	if (!parent) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	parent = dget(parent); | ||||
| 	mutex_lock(&parent->d_inode->i_mutex); | ||||
| 	if (dentry->d_inode) { | ||||
| 		if (S_ISDIR(dentry->d_inode->i_mode)) | ||||
| 			error = simple_rmdir(parent->d_inode, dentry); | ||||
| 		else | ||||
| 			error = simple_unlink(parent->d_inode, dentry); | ||||
| 		if (!error) | ||||
| 			d_delete(dentry); | ||||
| 	} | ||||
| 	if (!error) | ||||
| 		dput(dentry); | ||||
| 	mutex_unlock(&parent->d_inode->i_mutex); | ||||
| 	dput(parent); | ||||
| 
 | ||||
| 	if (!error) | ||||
| 		simple_release_fs(&relayfs_mount, &relayfs_mount_count); | ||||
| 
 | ||||
| 	return error; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relayfs_remove_file - remove a file from relay filesystem | ||||
|  *	@dentry: directory dentry | ||||
|  * | ||||
|  *	Returns 0 if successful, negative otherwise. | ||||
|  */ | ||||
| int relayfs_remove_file(struct dentry *dentry) | ||||
| { | ||||
| 	return relayfs_remove(dentry); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relayfs_remove_dir - remove a directory in the relay filesystem | ||||
|  *	@dentry: directory dentry | ||||
|  * | ||||
|  *	Returns 0 if successful, negative otherwise. | ||||
|  */ | ||||
| int relayfs_remove_dir(struct dentry *dentry) | ||||
| { | ||||
| 	return relayfs_remove(dentry); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_open - open file op for relay files | ||||
|  *	@inode: the inode | ||||
|  *	@filp: the file | ||||
|  * | ||||
|  *	Increments the channel buffer refcount. | ||||
|  */ | ||||
| static int relay_file_open(struct inode *inode, struct file *filp) | ||||
| { | ||||
| 	struct rchan_buf *buf = inode->u.generic_ip; | ||||
| 	kref_get(&buf->kref); | ||||
| 	filp->private_data = buf; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_mmap - mmap file op for relay files | ||||
|  *	@filp: the file | ||||
|  *	@vma: the vma describing what to map | ||||
|  * | ||||
|  *	Calls upon relay_mmap_buf to map the file into user space. | ||||
|  */ | ||||
| static int relay_file_mmap(struct file *filp, struct vm_area_struct *vma) | ||||
| { | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 	return relay_mmap_buf(buf, vma); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_poll - poll file op for relay files | ||||
|  *	@filp: the file | ||||
|  *	@wait: poll table | ||||
|  * | ||||
|  *	Poll implemention. | ||||
|  */ | ||||
| static unsigned int relay_file_poll(struct file *filp, poll_table *wait) | ||||
| { | ||||
| 	unsigned int mask = 0; | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 
 | ||||
| 	if (buf->finalized) | ||||
| 		return POLLERR; | ||||
| 
 | ||||
| 	if (filp->f_mode & FMODE_READ) { | ||||
| 		poll_wait(filp, &buf->read_wait, wait); | ||||
| 		if (!relay_buf_empty(buf)) | ||||
| 			mask |= POLLIN | POLLRDNORM; | ||||
| 	} | ||||
| 
 | ||||
| 	return mask; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_release - release file op for relay files | ||||
|  *	@inode: the inode | ||||
|  *	@filp: the file | ||||
|  * | ||||
|  *	Decrements the channel refcount, as the filesystem is | ||||
|  *	no longer using it. | ||||
|  */ | ||||
| static int relay_file_release(struct inode *inode, struct file *filp) | ||||
| { | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 	kref_put(&buf->kref, relay_remove_buf); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_consume - update the consumed count for the buffer | ||||
|  */ | ||||
| static void relay_file_read_consume(struct rchan_buf *buf, | ||||
| 				    size_t read_pos, | ||||
| 				    size_t bytes_consumed) | ||||
| { | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 	size_t read_subbuf; | ||||
| 
 | ||||
| 	if (buf->bytes_consumed + bytes_consumed > subbuf_size) { | ||||
| 		relay_subbufs_consumed(buf->chan, buf->cpu, 1); | ||||
| 		buf->bytes_consumed = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	buf->bytes_consumed += bytes_consumed; | ||||
| 	read_subbuf = read_pos / buf->chan->subbuf_size; | ||||
| 	if (buf->bytes_consumed + buf->padding[read_subbuf] == subbuf_size) { | ||||
| 		if ((read_subbuf == buf->subbufs_produced % n_subbufs) && | ||||
| 		    (buf->offset == subbuf_size)) | ||||
| 			return; | ||||
| 		relay_subbufs_consumed(buf->chan, buf->cpu, 1); | ||||
| 		buf->bytes_consumed = 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_avail - boolean, are there unconsumed bytes available? | ||||
|  */ | ||||
| static int relay_file_read_avail(struct rchan_buf *buf, size_t read_pos) | ||||
| { | ||||
| 	size_t bytes_produced, bytes_consumed, write_offset; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 	size_t produced = buf->subbufs_produced % n_subbufs; | ||||
| 	size_t consumed = buf->subbufs_consumed % n_subbufs; | ||||
| 
 | ||||
| 	write_offset = buf->offset > subbuf_size ? subbuf_size : buf->offset; | ||||
| 
 | ||||
| 	if (consumed > produced) { | ||||
| 		if ((produced > n_subbufs) && | ||||
| 		    (produced + n_subbufs - consumed <= n_subbufs)) | ||||
| 			produced += n_subbufs; | ||||
| 	} else if (consumed == produced) { | ||||
| 		if (buf->offset > subbuf_size) { | ||||
| 			produced += n_subbufs; | ||||
| 			if (buf->subbufs_produced == buf->subbufs_consumed) | ||||
| 				consumed += n_subbufs; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (buf->offset > subbuf_size) | ||||
| 		bytes_produced = (produced - 1) * subbuf_size + write_offset; | ||||
| 	else | ||||
| 		bytes_produced = produced * subbuf_size + write_offset; | ||||
| 	bytes_consumed = consumed * subbuf_size + buf->bytes_consumed; | ||||
| 
 | ||||
| 	if (bytes_produced == bytes_consumed) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	relay_file_read_consume(buf, read_pos, 0); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_subbuf_avail - return bytes available in sub-buffer | ||||
|  */ | ||||
| static size_t relay_file_read_subbuf_avail(size_t read_pos, | ||||
| 					   struct rchan_buf *buf) | ||||
| { | ||||
| 	size_t padding, avail = 0; | ||||
| 	size_t read_subbuf, read_offset, write_subbuf, write_offset; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 
 | ||||
| 	write_subbuf = (buf->data - buf->start) / subbuf_size; | ||||
| 	write_offset = buf->offset > subbuf_size ? subbuf_size : buf->offset; | ||||
| 	read_subbuf = read_pos / subbuf_size; | ||||
| 	read_offset = read_pos % subbuf_size; | ||||
| 	padding = buf->padding[read_subbuf]; | ||||
| 
 | ||||
| 	if (read_subbuf == write_subbuf) { | ||||
| 		if (read_offset + padding < write_offset) | ||||
| 			avail = write_offset - (read_offset + padding); | ||||
| 	} else | ||||
| 		avail = (subbuf_size - padding) - read_offset; | ||||
| 
 | ||||
| 	return avail; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_start_pos - find the first available byte to read | ||||
|  * | ||||
|  *	If the read_pos is in the middle of padding, return the | ||||
|  *	position of the first actually available byte, otherwise | ||||
|  *	return the original value. | ||||
|  */ | ||||
| static size_t relay_file_read_start_pos(size_t read_pos, | ||||
| 					struct rchan_buf *buf) | ||||
| { | ||||
| 	size_t read_subbuf, padding, padding_start, padding_end; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 
 | ||||
| 	read_subbuf = read_pos / subbuf_size; | ||||
| 	padding = buf->padding[read_subbuf]; | ||||
| 	padding_start = (read_subbuf + 1) * subbuf_size - padding; | ||||
| 	padding_end = (read_subbuf + 1) * subbuf_size; | ||||
| 	if (read_pos >= padding_start && read_pos < padding_end) { | ||||
| 		read_subbuf = (read_subbuf + 1) % n_subbufs; | ||||
| 		read_pos = read_subbuf * subbuf_size; | ||||
| 	} | ||||
| 
 | ||||
| 	return read_pos; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_end_pos - return the new read position | ||||
|  */ | ||||
| static size_t relay_file_read_end_pos(struct rchan_buf *buf, | ||||
| 				      size_t read_pos, | ||||
| 				      size_t count) | ||||
| { | ||||
| 	size_t read_subbuf, padding, end_pos; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 
 | ||||
| 	read_subbuf = read_pos / subbuf_size; | ||||
| 	padding = buf->padding[read_subbuf]; | ||||
| 	if (read_pos % subbuf_size + count + padding == subbuf_size) | ||||
| 		end_pos = (read_subbuf + 1) * subbuf_size; | ||||
| 	else | ||||
| 		end_pos = read_pos + count; | ||||
| 	if (end_pos >= subbuf_size * n_subbufs) | ||||
| 		end_pos = 0; | ||||
| 
 | ||||
| 	return end_pos; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read - read file op for relay files | ||||
|  *	@filp: the file | ||||
|  *	@buffer: the userspace buffer | ||||
|  *	@count: number of bytes to read | ||||
|  *	@ppos: position to read from | ||||
|  * | ||||
|  *	Reads count bytes or the number of bytes available in the | ||||
|  *	current sub-buffer being read, whichever is smaller. | ||||
|  */ | ||||
| static ssize_t relay_file_read(struct file *filp, | ||||
| 			       char __user *buffer, | ||||
| 			       size_t count, | ||||
| 			       loff_t *ppos) | ||||
| { | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 	struct inode *inode = filp->f_dentry->d_inode; | ||||
| 	size_t read_start, avail; | ||||
| 	ssize_t ret = 0; | ||||
| 	void *from; | ||||
| 
 | ||||
| 	mutex_lock(&inode->i_mutex); | ||||
| 	if(!relay_file_read_avail(buf, *ppos)) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	read_start = relay_file_read_start_pos(*ppos, buf); | ||||
| 	avail = relay_file_read_subbuf_avail(read_start, buf); | ||||
| 	if (!avail) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	from = buf->start + read_start; | ||||
| 	ret = count = min(count, avail); | ||||
| 	if (copy_to_user(buffer, from, count)) { | ||||
| 		ret = -EFAULT; | ||||
| 		goto out; | ||||
| 	} | ||||
| 	relay_file_read_consume(buf, read_start, count); | ||||
| 	*ppos = relay_file_read_end_pos(buf, read_start, count); | ||||
| out: | ||||
| 	mutex_unlock(&inode->i_mutex); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| struct file_operations relay_file_operations = { | ||||
| 	.open		= relay_file_open, | ||||
| 	.poll		= relay_file_poll, | ||||
| 	.mmap		= relay_file_mmap, | ||||
| 	.read		= relay_file_read, | ||||
| 	.llseek		= no_llseek, | ||||
| 	.release	= relay_file_release, | ||||
| }; | ||||
| 
 | ||||
| static struct super_operations relayfs_ops = { | ||||
| 	.statfs		= simple_statfs, | ||||
| 	.drop_inode	= generic_delete_inode, | ||||
| }; | ||||
| 
 | ||||
| static int relayfs_fill_super(struct super_block * sb, void * data, int silent) | ||||
| { | ||||
| 	struct inode *inode; | ||||
| 	struct dentry *root; | ||||
| 	int mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; | ||||
| 
 | ||||
| 	sb->s_blocksize = PAGE_CACHE_SIZE; | ||||
| 	sb->s_blocksize_bits = PAGE_CACHE_SHIFT; | ||||
| 	sb->s_magic = RELAYFS_MAGIC; | ||||
| 	sb->s_op = &relayfs_ops; | ||||
| 	inode = relayfs_get_inode(sb, mode, NULL, NULL); | ||||
| 
 | ||||
| 	if (!inode) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	root = d_alloc_root(inode); | ||||
| 	if (!root) { | ||||
| 		iput(inode); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	sb->s_root = root; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static struct super_block * relayfs_get_sb(struct file_system_type *fs_type, | ||||
| 					   int flags, const char *dev_name, | ||||
| 					   void *data) | ||||
| { | ||||
| 	return get_sb_single(fs_type, flags, data, relayfs_fill_super); | ||||
| } | ||||
| 
 | ||||
| static struct file_system_type relayfs_fs_type = { | ||||
| 	.owner		= THIS_MODULE, | ||||
| 	.name		= "relayfs", | ||||
| 	.get_sb		= relayfs_get_sb, | ||||
| 	.kill_sb	= kill_litter_super, | ||||
| }; | ||||
| 
 | ||||
| static int __init init_relayfs_fs(void) | ||||
| { | ||||
| 	return register_filesystem(&relayfs_fs_type); | ||||
| } | ||||
| 
 | ||||
| static void __exit exit_relayfs_fs(void) | ||||
| { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	unregister_filesystem(&relayfs_fs_type); | ||||
| } | ||||
| 
 | ||||
| module_init(init_relayfs_fs) | ||||
| module_exit(exit_relayfs_fs) | ||||
| 
 | ||||
| EXPORT_SYMBOL_GPL(relay_file_operations); | ||||
| EXPORT_SYMBOL_GPL(relayfs_create_dir); | ||||
| EXPORT_SYMBOL_GPL(relayfs_remove_dir); | ||||
| EXPORT_SYMBOL_GPL(relayfs_create_file); | ||||
| EXPORT_SYMBOL_GPL(relayfs_remove_file); | ||||
| 
 | ||||
| MODULE_AUTHOR("Tom Zanussi <zanussi@us.ibm.com> and Karim Yaghmour <karim@opersys.com>"); | ||||
| MODULE_DESCRIPTION("Relay Filesystem"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| 
 | ||||
|  | @ -1,482 +0,0 @@ | |||
| /*
 | ||||
|  * Public API and common code for RelayFS. | ||||
|  * | ||||
|  * See Documentation/filesystems/relayfs.txt for an overview of relayfs. | ||||
|  * | ||||
|  * Copyright (C) 2002-2005 - Tom Zanussi (zanussi@us.ibm.com), IBM Corp | ||||
|  * Copyright (C) 1999-2005 - Karim Yaghmour (karim@opersys.com) | ||||
|  * | ||||
|  * This file is released under the GPL. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/errno.h> | ||||
| #include <linux/stddef.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/string.h> | ||||
| #include <linux/relayfs_fs.h> | ||||
| #include "relay.h" | ||||
| #include "buffers.h" | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_buf_empty - boolean, is the channel buffer empty? | ||||
|  *	@buf: channel buffer | ||||
|  * | ||||
|  *	Returns 1 if the buffer is empty, 0 otherwise. | ||||
|  */ | ||||
| int relay_buf_empty(struct rchan_buf *buf) | ||||
| { | ||||
| 	return (buf->subbufs_produced - buf->subbufs_consumed) ? 0 : 1; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_buf_full - boolean, is the channel buffer full? | ||||
|  *	@buf: channel buffer | ||||
|  * | ||||
|  *	Returns 1 if the buffer is full, 0 otherwise. | ||||
|  */ | ||||
| int relay_buf_full(struct rchan_buf *buf) | ||||
| { | ||||
| 	size_t ready = buf->subbufs_produced - buf->subbufs_consumed; | ||||
| 	return (ready >= buf->chan->n_subbufs) ? 1 : 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * High-level relayfs kernel API and associated functions. | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * rchan_callback implementations defining default channel behavior.  Used | ||||
|  * in place of corresponding NULL values in client callback struct. | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * subbuf_start() default callback.  Does nothing. | ||||
|  */ | ||||
| static int subbuf_start_default_callback (struct rchan_buf *buf, | ||||
| 					  void *subbuf, | ||||
| 					  void *prev_subbuf, | ||||
| 					  size_t prev_padding) | ||||
| { | ||||
| 	if (relay_buf_full(buf)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * buf_mapped() default callback.  Does nothing. | ||||
|  */ | ||||
| static void buf_mapped_default_callback(struct rchan_buf *buf, | ||||
| 					struct file *filp) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * buf_unmapped() default callback.  Does nothing. | ||||
|  */ | ||||
| static void buf_unmapped_default_callback(struct rchan_buf *buf, | ||||
| 					  struct file *filp) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * create_buf_file_create() default callback.  Creates file to represent buf. | ||||
|  */ | ||||
| static struct dentry *create_buf_file_default_callback(const char *filename, | ||||
| 						       struct dentry *parent, | ||||
| 						       int mode, | ||||
| 						       struct rchan_buf *buf, | ||||
| 						       int *is_global) | ||||
| { | ||||
| 	return relayfs_create_file(filename, parent, mode, | ||||
| 				   &relay_file_operations, buf); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * remove_buf_file() default callback.  Removes file representing relay buffer. | ||||
|  */ | ||||
| static int remove_buf_file_default_callback(struct dentry *dentry) | ||||
| { | ||||
| 	return relayfs_remove(dentry); | ||||
| } | ||||
| 
 | ||||
| /* relay channel default callbacks */ | ||||
| static struct rchan_callbacks default_channel_callbacks = { | ||||
| 	.subbuf_start = subbuf_start_default_callback, | ||||
| 	.buf_mapped = buf_mapped_default_callback, | ||||
| 	.buf_unmapped = buf_unmapped_default_callback, | ||||
| 	.create_buf_file = create_buf_file_default_callback, | ||||
| 	.remove_buf_file = remove_buf_file_default_callback, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  *	wakeup_readers - wake up readers waiting on a channel | ||||
|  *	@private: the channel buffer | ||||
|  * | ||||
|  *	This is the work function used to defer reader waking.  The | ||||
|  *	reason waking is deferred is that calling directly from write | ||||
|  *	causes problems if you're writing from say the scheduler. | ||||
|  */ | ||||
| static void wakeup_readers(void *private) | ||||
| { | ||||
| 	struct rchan_buf *buf = private; | ||||
| 	wake_up_interruptible(&buf->read_wait); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	__relay_reset - reset a channel buffer | ||||
|  *	@buf: the channel buffer | ||||
|  *	@init: 1 if this is a first-time initialization | ||||
|  * | ||||
|  *	See relay_reset for description of effect. | ||||
|  */ | ||||
| static inline void __relay_reset(struct rchan_buf *buf, unsigned int init) | ||||
| { | ||||
| 	size_t i; | ||||
| 
 | ||||
| 	if (init) { | ||||
| 		init_waitqueue_head(&buf->read_wait); | ||||
| 		kref_init(&buf->kref); | ||||
| 		INIT_WORK(&buf->wake_readers, NULL, NULL); | ||||
| 	} else { | ||||
| 		cancel_delayed_work(&buf->wake_readers); | ||||
| 		flush_scheduled_work(); | ||||
| 	} | ||||
| 
 | ||||
| 	buf->subbufs_produced = 0; | ||||
| 	buf->subbufs_consumed = 0; | ||||
| 	buf->bytes_consumed = 0; | ||||
| 	buf->finalized = 0; | ||||
| 	buf->data = buf->start; | ||||
| 	buf->offset = 0; | ||||
| 
 | ||||
| 	for (i = 0; i < buf->chan->n_subbufs; i++) | ||||
| 		buf->padding[i] = 0; | ||||
| 
 | ||||
| 	buf->chan->cb->subbuf_start(buf, buf->data, NULL, 0); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_reset - reset the channel | ||||
|  *	@chan: the channel | ||||
|  * | ||||
|  *	This has the effect of erasing all data from all channel buffers | ||||
|  *	and restarting the channel in its initial state.  The buffers | ||||
|  *	are not freed, so any mappings are still in effect. | ||||
|  * | ||||
|  *	NOTE: Care should be taken that the channel isn't actually | ||||
|  *	being used by anything when this call is made. | ||||
|  */ | ||||
| void relay_reset(struct rchan *chan) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan_buf *prev = NULL; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i] || chan->buf[i] == prev) | ||||
| 			break; | ||||
| 		__relay_reset(chan->buf[i], 0); | ||||
| 		prev = chan->buf[i]; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_open_buf - create a new channel buffer in relayfs | ||||
|  * | ||||
|  *	Internal - used by relay_open(). | ||||
|  */ | ||||
| static struct rchan_buf *relay_open_buf(struct rchan *chan, | ||||
| 					const char *filename, | ||||
| 					struct dentry *parent, | ||||
| 					int *is_global) | ||||
| { | ||||
| 	struct rchan_buf *buf; | ||||
| 	struct dentry *dentry; | ||||
| 
 | ||||
| 	if (*is_global) | ||||
| 		return chan->buf[0]; | ||||
| 
 | ||||
|  	buf = relay_create_buf(chan); | ||||
|  	if (!buf) | ||||
|  		return NULL; | ||||
| 
 | ||||
| 	/* Create file in fs */ | ||||
|  	dentry = chan->cb->create_buf_file(filename, parent, S_IRUSR, | ||||
|  					   buf, is_global); | ||||
|  	if (!dentry) { | ||||
|  		relay_destroy_buf(buf); | ||||
| 		return NULL; | ||||
|  	} | ||||
| 
 | ||||
| 	buf->dentry = dentry; | ||||
| 	__relay_reset(buf, 1); | ||||
| 
 | ||||
| 	return buf; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_close_buf - close a channel buffer | ||||
|  *	@buf: channel buffer | ||||
|  * | ||||
|  *	Marks the buffer finalized and restores the default callbacks. | ||||
|  *	The channel buffer and channel buffer data structure are then freed | ||||
|  *	automatically when the last reference is given up. | ||||
|  */ | ||||
| static inline void relay_close_buf(struct rchan_buf *buf) | ||||
| { | ||||
| 	buf->finalized = 1; | ||||
| 	buf->chan->cb = &default_channel_callbacks; | ||||
| 	cancel_delayed_work(&buf->wake_readers); | ||||
| 	flush_scheduled_work(); | ||||
| 	kref_put(&buf->kref, relay_remove_buf); | ||||
| } | ||||
| 
 | ||||
| static inline void setup_callbacks(struct rchan *chan, | ||||
| 				   struct rchan_callbacks *cb) | ||||
| { | ||||
| 	if (!cb) { | ||||
| 		chan->cb = &default_channel_callbacks; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!cb->subbuf_start) | ||||
| 		cb->subbuf_start = subbuf_start_default_callback; | ||||
| 	if (!cb->buf_mapped) | ||||
| 		cb->buf_mapped = buf_mapped_default_callback; | ||||
| 	if (!cb->buf_unmapped) | ||||
| 		cb->buf_unmapped = buf_unmapped_default_callback; | ||||
| 	if (!cb->create_buf_file) | ||||
| 		cb->create_buf_file = create_buf_file_default_callback; | ||||
| 	if (!cb->remove_buf_file) | ||||
| 		cb->remove_buf_file = remove_buf_file_default_callback; | ||||
| 	chan->cb = cb; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_open - create a new relayfs channel | ||||
|  *	@base_filename: base name of files to create | ||||
|  *	@parent: dentry of parent directory, NULL for root directory | ||||
|  *	@subbuf_size: size of sub-buffers | ||||
|  *	@n_subbufs: number of sub-buffers | ||||
|  *	@cb: client callback functions | ||||
|  * | ||||
|  *	Returns channel pointer if successful, NULL otherwise. | ||||
|  * | ||||
|  *	Creates a channel buffer for each cpu using the sizes and | ||||
|  *	attributes specified.  The created channel buffer files | ||||
|  *	will be named base_filename0...base_filenameN-1.  File | ||||
|  *	permissions will be S_IRUSR. | ||||
|  */ | ||||
| struct rchan *relay_open(const char *base_filename, | ||||
| 			 struct dentry *parent, | ||||
| 			 size_t subbuf_size, | ||||
| 			 size_t n_subbufs, | ||||
| 			 struct rchan_callbacks *cb) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan *chan; | ||||
| 	char *tmpname; | ||||
| 	int is_global = 0; | ||||
| 
 | ||||
| 	if (!base_filename) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	if (!(subbuf_size && n_subbufs)) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	chan = kcalloc(1, sizeof(struct rchan), GFP_KERNEL); | ||||
| 	if (!chan) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	chan->version = RELAYFS_CHANNEL_VERSION; | ||||
| 	chan->n_subbufs = n_subbufs; | ||||
| 	chan->subbuf_size = subbuf_size; | ||||
| 	chan->alloc_size = FIX_SIZE(subbuf_size * n_subbufs); | ||||
| 	setup_callbacks(chan, cb); | ||||
| 	kref_init(&chan->kref); | ||||
| 
 | ||||
| 	tmpname = kmalloc(NAME_MAX + 1, GFP_KERNEL); | ||||
| 	if (!tmpname) | ||||
| 		goto free_chan; | ||||
| 
 | ||||
| 	for_each_online_cpu(i) { | ||||
| 		sprintf(tmpname, "%s%d", base_filename, i); | ||||
| 		chan->buf[i] = relay_open_buf(chan, tmpname, parent, | ||||
| 					      &is_global); | ||||
| 		chan->buf[i]->cpu = i; | ||||
| 		if (!chan->buf[i]) | ||||
| 			goto free_bufs; | ||||
| 	} | ||||
| 
 | ||||
| 	kfree(tmpname); | ||||
| 	return chan; | ||||
| 
 | ||||
| free_bufs: | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i]) | ||||
| 			break; | ||||
| 		relay_close_buf(chan->buf[i]); | ||||
| 		if (is_global) | ||||
| 			break; | ||||
| 	} | ||||
| 	kfree(tmpname); | ||||
| 
 | ||||
| free_chan: | ||||
| 	kref_put(&chan->kref, relay_destroy_channel); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_switch_subbuf - switch to a new sub-buffer | ||||
|  *	@buf: channel buffer | ||||
|  *	@length: size of current event | ||||
|  * | ||||
|  *	Returns either the length passed in or 0 if full. | ||||
| 
 | ||||
|  *	Performs sub-buffer-switch tasks such as invoking callbacks, | ||||
|  *	updating padding counts, waking up readers, etc. | ||||
|  */ | ||||
| size_t relay_switch_subbuf(struct rchan_buf *buf, size_t length) | ||||
| { | ||||
| 	void *old, *new; | ||||
| 	size_t old_subbuf, new_subbuf; | ||||
| 
 | ||||
| 	if (unlikely(length > buf->chan->subbuf_size)) | ||||
| 		goto toobig; | ||||
| 
 | ||||
| 	if (buf->offset != buf->chan->subbuf_size + 1) { | ||||
| 		buf->prev_padding = buf->chan->subbuf_size - buf->offset; | ||||
| 		old_subbuf = buf->subbufs_produced % buf->chan->n_subbufs; | ||||
| 		buf->padding[old_subbuf] = buf->prev_padding; | ||||
| 		buf->subbufs_produced++; | ||||
| 		if (waitqueue_active(&buf->read_wait)) { | ||||
| 			PREPARE_WORK(&buf->wake_readers, wakeup_readers, buf); | ||||
| 			schedule_delayed_work(&buf->wake_readers, 1); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	old = buf->data; | ||||
| 	new_subbuf = buf->subbufs_produced % buf->chan->n_subbufs; | ||||
| 	new = buf->start + new_subbuf * buf->chan->subbuf_size; | ||||
| 	buf->offset = 0; | ||||
| 	if (!buf->chan->cb->subbuf_start(buf, new, old, buf->prev_padding)) { | ||||
| 		buf->offset = buf->chan->subbuf_size + 1; | ||||
| 		return 0; | ||||
| 	} | ||||
| 	buf->data = new; | ||||
| 	buf->padding[new_subbuf] = 0; | ||||
| 
 | ||||
| 	if (unlikely(length + buf->offset > buf->chan->subbuf_size)) | ||||
| 		goto toobig; | ||||
| 
 | ||||
| 	return length; | ||||
| 
 | ||||
| toobig: | ||||
| 	buf->chan->last_toobig = length; | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_subbufs_consumed - update the buffer's sub-buffers-consumed count | ||||
|  *	@chan: the channel | ||||
|  *	@cpu: the cpu associated with the channel buffer to update | ||||
|  *	@subbufs_consumed: number of sub-buffers to add to current buf's count | ||||
|  * | ||||
|  *	Adds to the channel buffer's consumed sub-buffer count. | ||||
|  *	subbufs_consumed should be the number of sub-buffers newly consumed, | ||||
|  *	not the total consumed. | ||||
|  * | ||||
|  *	NOTE: kernel clients don't need to call this function if the channel | ||||
|  *	mode is 'overwrite'. | ||||
|  */ | ||||
| void relay_subbufs_consumed(struct rchan *chan, | ||||
| 			    unsigned int cpu, | ||||
| 			    size_t subbufs_consumed) | ||||
| { | ||||
| 	struct rchan_buf *buf; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (cpu >= NR_CPUS || !chan->buf[cpu]) | ||||
| 		return; | ||||
| 
 | ||||
| 	buf = chan->buf[cpu]; | ||||
| 	buf->subbufs_consumed += subbufs_consumed; | ||||
| 	if (buf->subbufs_consumed > buf->subbufs_produced) | ||||
| 		buf->subbufs_consumed = buf->subbufs_produced; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_destroy_channel - free the channel struct | ||||
|  * | ||||
|  *	Should only be called from kref_put(). | ||||
|  */ | ||||
| void relay_destroy_channel(struct kref *kref) | ||||
| { | ||||
| 	struct rchan *chan = container_of(kref, struct rchan, kref); | ||||
| 	kfree(chan); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_close - close the channel | ||||
|  *	@chan: the channel | ||||
|  * | ||||
|  *	Closes all channel buffers and frees the channel. | ||||
|  */ | ||||
| void relay_close(struct rchan *chan) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan_buf *prev = NULL; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i] || chan->buf[i] == prev) | ||||
| 			break; | ||||
| 		relay_close_buf(chan->buf[i]); | ||||
| 		prev = chan->buf[i]; | ||||
| 	} | ||||
| 
 | ||||
| 	if (chan->last_toobig) | ||||
| 		printk(KERN_WARNING "relayfs: one or more items not logged " | ||||
| 		       "[item size (%Zd) > sub-buffer size (%Zd)]\n", | ||||
| 		       chan->last_toobig, chan->subbuf_size); | ||||
| 
 | ||||
| 	kref_put(&chan->kref, relay_destroy_channel); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_flush - close the channel | ||||
|  *	@chan: the channel | ||||
|  * | ||||
|  *	Flushes all channel buffers i.e. forces buffer switch. | ||||
|  */ | ||||
| void relay_flush(struct rchan *chan) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan_buf *prev = NULL; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i] || chan->buf[i] == prev) | ||||
| 			break; | ||||
| 		relay_switch_subbuf(chan->buf[i], 0); | ||||
| 		prev = chan->buf[i]; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| EXPORT_SYMBOL_GPL(relay_open); | ||||
| EXPORT_SYMBOL_GPL(relay_close); | ||||
| EXPORT_SYMBOL_GPL(relay_flush); | ||||
| EXPORT_SYMBOL_GPL(relay_reset); | ||||
| EXPORT_SYMBOL_GPL(relay_subbufs_consumed); | ||||
| EXPORT_SYMBOL_GPL(relay_switch_subbuf); | ||||
| EXPORT_SYMBOL_GPL(relay_buf_full); | ||||
|  | @ -1,8 +0,0 @@ | |||
| #ifndef _RELAY_H | ||||
| #define _RELAY_H | ||||
| 
 | ||||
| extern int relayfs_remove(struct dentry *dentry); | ||||
| extern int relay_buf_empty(struct rchan_buf *buf); | ||||
| extern void relay_destroy_channel(struct kref *kref); | ||||
| 
 | ||||
| #endif /* _RELAY_H */ | ||||
							
								
								
									
										281
									
								
								include/linux/relay.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								include/linux/relay.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,281 @@ | |||
| /*
 | ||||
|  * linux/include/linux/relay.h | ||||
|  * | ||||
|  * Copyright (C) 2002, 2003 - Tom Zanussi (zanussi@us.ibm.com), IBM Corp | ||||
|  * Copyright (C) 1999, 2000, 2001, 2002 - Karim Yaghmour (karim@opersys.com) | ||||
|  * | ||||
|  * CONFIG_RELAY definitions and declarations | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _LINUX_RELAY_H | ||||
| #define _LINUX_RELAY_H | ||||
| 
 | ||||
| #include <linux/config.h> | ||||
| #include <linux/types.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/wait.h> | ||||
| #include <linux/list.h> | ||||
| #include <linux/fs.h> | ||||
| #include <linux/poll.h> | ||||
| #include <linux/kref.h> | ||||
| 
 | ||||
| /* Needs a _much_ better name... */ | ||||
| #define FIX_SIZE(x) ((((x) - 1) & PAGE_MASK) + PAGE_SIZE) | ||||
| 
 | ||||
| /*
 | ||||
|  * Tracks changes to rchan/rchan_buf structs | ||||
|  */ | ||||
| #define RELAYFS_CHANNEL_VERSION		6 | ||||
| 
 | ||||
| /*
 | ||||
|  * Per-cpu relay channel buffer | ||||
|  */ | ||||
| struct rchan_buf | ||||
| { | ||||
| 	void *start;			/* start of channel buffer */ | ||||
| 	void *data;			/* start of current sub-buffer */ | ||||
| 	size_t offset;			/* current offset into sub-buffer */ | ||||
| 	size_t subbufs_produced;	/* count of sub-buffers produced */ | ||||
| 	size_t subbufs_consumed;	/* count of sub-buffers consumed */ | ||||
| 	struct rchan *chan;		/* associated channel */ | ||||
| 	wait_queue_head_t read_wait;	/* reader wait queue */ | ||||
| 	struct work_struct wake_readers; /* reader wake-up work struct */ | ||||
| 	struct dentry *dentry;		/* channel file dentry */ | ||||
| 	struct kref kref;		/* channel buffer refcount */ | ||||
| 	struct page **page_array;	/* array of current buffer pages */ | ||||
| 	unsigned int page_count;	/* number of current buffer pages */ | ||||
| 	unsigned int finalized;		/* buffer has been finalized */ | ||||
| 	size_t *padding;		/* padding counts per sub-buffer */ | ||||
| 	size_t prev_padding;		/* temporary variable */ | ||||
| 	size_t bytes_consumed;		/* bytes consumed in cur read subbuf */ | ||||
| 	unsigned int cpu;		/* this buf's cpu */ | ||||
| } ____cacheline_aligned; | ||||
| 
 | ||||
| /*
 | ||||
|  * Relay channel data structure | ||||
|  */ | ||||
| struct rchan | ||||
| { | ||||
| 	u32 version;			/* the version of this struct */ | ||||
| 	size_t subbuf_size;		/* sub-buffer size */ | ||||
| 	size_t n_subbufs;		/* number of sub-buffers per buffer */ | ||||
| 	size_t alloc_size;		/* total buffer size allocated */ | ||||
| 	struct rchan_callbacks *cb;	/* client callbacks */ | ||||
| 	struct kref kref;		/* channel refcount */ | ||||
| 	void *private_data;		/* for user-defined data */ | ||||
| 	size_t last_toobig;		/* tried to log event > subbuf size */ | ||||
| 	struct rchan_buf *buf[NR_CPUS]; /* per-cpu channel buffers */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * Relay channel client callbacks | ||||
|  */ | ||||
| struct rchan_callbacks | ||||
| { | ||||
| 	/*
 | ||||
| 	 * subbuf_start - called on buffer-switch to a new sub-buffer | ||||
| 	 * @buf: the channel buffer containing the new sub-buffer | ||||
| 	 * @subbuf: the start of the new sub-buffer | ||||
| 	 * @prev_subbuf: the start of the previous sub-buffer | ||||
| 	 * @prev_padding: unused space at the end of previous sub-buffer | ||||
| 	 * | ||||
| 	 * The client should return 1 to continue logging, 0 to stop | ||||
| 	 * logging. | ||||
| 	 * | ||||
| 	 * NOTE: subbuf_start will also be invoked when the buffer is | ||||
| 	 *       created, so that the first sub-buffer can be initialized | ||||
| 	 *       if necessary.  In this case, prev_subbuf will be NULL. | ||||
| 	 * | ||||
| 	 * NOTE: the client can reserve bytes at the beginning of the new | ||||
| 	 *       sub-buffer by calling subbuf_start_reserve() in this callback. | ||||
| 	 */ | ||||
| 	int (*subbuf_start) (struct rchan_buf *buf, | ||||
| 			     void *subbuf, | ||||
| 			     void *prev_subbuf, | ||||
| 			     size_t prev_padding); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * buf_mapped - relay buffer mmap notification | ||||
| 	 * @buf: the channel buffer | ||||
| 	 * @filp: relay file pointer | ||||
| 	 * | ||||
| 	 * Called when a relay file is successfully mmapped | ||||
| 	 */ | ||||
|         void (*buf_mapped)(struct rchan_buf *buf, | ||||
| 			   struct file *filp); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * buf_unmapped - relay buffer unmap notification | ||||
| 	 * @buf: the channel buffer | ||||
| 	 * @filp: relay file pointer | ||||
| 	 * | ||||
| 	 * Called when a relay file is successfully unmapped | ||||
| 	 */ | ||||
|         void (*buf_unmapped)(struct rchan_buf *buf, | ||||
| 			     struct file *filp); | ||||
| 	/*
 | ||||
| 	 * create_buf_file - create file to represent a relay channel buffer | ||||
| 	 * @filename: the name of the file to create | ||||
| 	 * @parent: the parent of the file to create | ||||
| 	 * @mode: the mode of the file to create | ||||
| 	 * @buf: the channel buffer | ||||
| 	 * @is_global: outparam - set non-zero if the buffer should be global | ||||
| 	 * | ||||
| 	 * Called during relay_open(), once for each per-cpu buffer, | ||||
| 	 * to allow the client to create a file to be used to | ||||
| 	 * represent the corresponding channel buffer.  If the file is | ||||
| 	 * created outside of relay, the parent must also exist in | ||||
| 	 * that filesystem. | ||||
| 	 * | ||||
| 	 * The callback should return the dentry of the file created | ||||
| 	 * to represent the relay buffer. | ||||
| 	 * | ||||
| 	 * Setting the is_global outparam to a non-zero value will | ||||
| 	 * cause relay_open() to create a single global buffer rather | ||||
| 	 * than the default set of per-cpu buffers. | ||||
| 	 * | ||||
| 	 * See Documentation/filesystems/relayfs.txt for more info. | ||||
| 	 */ | ||||
| 	struct dentry *(*create_buf_file)(const char *filename, | ||||
| 					  struct dentry *parent, | ||||
| 					  int mode, | ||||
| 					  struct rchan_buf *buf, | ||||
| 					  int *is_global); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * remove_buf_file - remove file representing a relay channel buffer | ||||
| 	 * @dentry: the dentry of the file to remove | ||||
| 	 * | ||||
| 	 * Called during relay_close(), once for each per-cpu buffer, | ||||
| 	 * to allow the client to remove a file used to represent a | ||||
| 	 * channel buffer. | ||||
| 	 * | ||||
| 	 * The callback should return 0 if successful, negative if not. | ||||
| 	 */ | ||||
| 	int (*remove_buf_file)(struct dentry *dentry); | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * CONFIG_RELAY kernel API, kernel/relay.c | ||||
|  */ | ||||
| 
 | ||||
| struct rchan *relay_open(const char *base_filename, | ||||
| 			 struct dentry *parent, | ||||
| 			 size_t subbuf_size, | ||||
| 			 size_t n_subbufs, | ||||
| 			 struct rchan_callbacks *cb); | ||||
| extern void relay_close(struct rchan *chan); | ||||
| extern void relay_flush(struct rchan *chan); | ||||
| extern void relay_subbufs_consumed(struct rchan *chan, | ||||
| 				   unsigned int cpu, | ||||
| 				   size_t consumed); | ||||
| extern void relay_reset(struct rchan *chan); | ||||
| extern int relay_buf_full(struct rchan_buf *buf); | ||||
| 
 | ||||
| extern size_t relay_switch_subbuf(struct rchan_buf *buf, | ||||
| 				  size_t length); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_write - write data into the channel | ||||
|  *	@chan: relay channel | ||||
|  *	@data: data to be written | ||||
|  *	@length: number of bytes to write | ||||
|  * | ||||
|  *	Writes data into the current cpu's channel buffer. | ||||
|  * | ||||
|  *	Protects the buffer by disabling interrupts.  Use this | ||||
|  *	if you might be logging from interrupt context.  Try | ||||
|  *	__relay_write() if you know you	won't be logging from | ||||
|  *	interrupt context. | ||||
|  */ | ||||
| static inline void relay_write(struct rchan *chan, | ||||
| 			       const void *data, | ||||
| 			       size_t length) | ||||
| { | ||||
| 	unsigned long flags; | ||||
| 	struct rchan_buf *buf; | ||||
| 
 | ||||
| 	local_irq_save(flags); | ||||
| 	buf = chan->buf[smp_processor_id()]; | ||||
| 	if (unlikely(buf->offset + length > chan->subbuf_size)) | ||||
| 		length = relay_switch_subbuf(buf, length); | ||||
| 	memcpy(buf->data + buf->offset, data, length); | ||||
| 	buf->offset += length; | ||||
| 	local_irq_restore(flags); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	__relay_write - write data into the channel | ||||
|  *	@chan: relay channel | ||||
|  *	@data: data to be written | ||||
|  *	@length: number of bytes to write | ||||
|  * | ||||
|  *	Writes data into the current cpu's channel buffer. | ||||
|  * | ||||
|  *	Protects the buffer by disabling preemption.  Use | ||||
|  *	relay_write() if you might be logging from interrupt | ||||
|  *	context. | ||||
|  */ | ||||
| static inline void __relay_write(struct rchan *chan, | ||||
| 				 const void *data, | ||||
| 				 size_t length) | ||||
| { | ||||
| 	struct rchan_buf *buf; | ||||
| 
 | ||||
| 	buf = chan->buf[get_cpu()]; | ||||
| 	if (unlikely(buf->offset + length > buf->chan->subbuf_size)) | ||||
| 		length = relay_switch_subbuf(buf, length); | ||||
| 	memcpy(buf->data + buf->offset, data, length); | ||||
| 	buf->offset += length; | ||||
| 	put_cpu(); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_reserve - reserve slot in channel buffer | ||||
|  *	@chan: relay channel | ||||
|  *	@length: number of bytes to reserve | ||||
|  * | ||||
|  *	Returns pointer to reserved slot, NULL if full. | ||||
|  * | ||||
|  *	Reserves a slot in the current cpu's channel buffer. | ||||
|  *	Does not protect the buffer at all - caller must provide | ||||
|  *	appropriate synchronization. | ||||
|  */ | ||||
| static inline void *relay_reserve(struct rchan *chan, size_t length) | ||||
| { | ||||
| 	void *reserved; | ||||
| 	struct rchan_buf *buf = chan->buf[smp_processor_id()]; | ||||
| 
 | ||||
| 	if (unlikely(buf->offset + length > buf->chan->subbuf_size)) { | ||||
| 		length = relay_switch_subbuf(buf, length); | ||||
| 		if (!length) | ||||
| 			return NULL; | ||||
| 	} | ||||
| 	reserved = buf->data + buf->offset; | ||||
| 	buf->offset += length; | ||||
| 
 | ||||
| 	return reserved; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	subbuf_start_reserve - reserve bytes at the start of a sub-buffer | ||||
|  *	@buf: relay channel buffer | ||||
|  *	@length: number of bytes to reserve | ||||
|  * | ||||
|  *	Helper function used to reserve bytes at the beginning of | ||||
|  *	a sub-buffer in the subbuf_start() callback. | ||||
|  */ | ||||
| static inline void subbuf_start_reserve(struct rchan_buf *buf, | ||||
| 					size_t length) | ||||
| { | ||||
| 	BUG_ON(length >= buf->chan->subbuf_size - 1); | ||||
| 	buf->offset = length; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * exported relay file operations, kernel/relay.c | ||||
|  */ | ||||
| extern struct file_operations relay_file_operations; | ||||
| 
 | ||||
| #endif /* _LINUX_RELAY_H */ | ||||
| 
 | ||||
							
								
								
									
										11
									
								
								init/Kconfig
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								init/Kconfig
									
									
									
									
									
								
							|  | @ -214,6 +214,17 @@ config CPUSETS | |||
| 
 | ||||
| 	  Say N if unsure. | ||||
| 
 | ||||
| config RELAY | ||||
| 	bool "Kernel->user space relay support (formerly relayfs)" | ||||
| 	help | ||||
| 	  This option enables support for relay interface support in | ||||
| 	  certain file systems (such as debugfs). | ||||
| 	  It is designed to provide an efficient mechanism for tools and | ||||
| 	  facilities to relay large amounts of data from kernel space to | ||||
| 	  user space. | ||||
| 
 | ||||
| 	  If unsure, say N. | ||||
| 
 | ||||
| source "usr/Kconfig" | ||||
| 
 | ||||
| config UID16 | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o | |||
| obj-$(CONFIG_GENERIC_HARDIRQS) += irq/ | ||||
| obj-$(CONFIG_SECCOMP) += seccomp.o | ||||
| obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o | ||||
| obj-$(CONFIG_RELAY) += relay.o | ||||
| 
 | ||||
| ifneq ($(CONFIG_SCHED_NO_NO_OMIT_FRAME_POINTER),y) | ||||
| # According to Alan Modra <alan@linuxcare.com.au>, the -fno-omit-frame-pointer is
 | ||||
|  |  | |||
							
								
								
									
										919
									
								
								kernel/relay.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										919
									
								
								kernel/relay.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,919 @@ | |||
| /*
 | ||||
|  * Public API and common code for kernel->userspace relay file support. | ||||
|  * | ||||
|  * See Documentation/filesystems/relayfs.txt for an overview of relayfs. | ||||
|  * | ||||
|  * Copyright (C) 2002-2005 - Tom Zanussi (zanussi@us.ibm.com), IBM Corp | ||||
|  * Copyright (C) 1999-2005 - Karim Yaghmour (karim@opersys.com) | ||||
|  * | ||||
|  * Moved to kernel/relay.c by Paul Mundt, 2006. | ||||
|  * | ||||
|  * This file is released under the GPL. | ||||
|  */ | ||||
| #include <linux/errno.h> | ||||
| #include <linux/stddef.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/string.h> | ||||
| #include <linux/relay.h> | ||||
| #include <linux/vmalloc.h> | ||||
| #include <linux/mm.h> | ||||
| 
 | ||||
| /*
 | ||||
|  * close() vm_op implementation for relay file mapping. | ||||
|  */ | ||||
| static void relay_file_mmap_close(struct vm_area_struct *vma) | ||||
| { | ||||
| 	struct rchan_buf *buf = vma->vm_private_data; | ||||
| 	buf->chan->cb->buf_unmapped(buf, vma->vm_file); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * nopage() vm_op implementation for relay file mapping. | ||||
|  */ | ||||
| static struct page *relay_buf_nopage(struct vm_area_struct *vma, | ||||
| 				     unsigned long address, | ||||
| 				     int *type) | ||||
| { | ||||
| 	struct page *page; | ||||
| 	struct rchan_buf *buf = vma->vm_private_data; | ||||
| 	unsigned long offset = address - vma->vm_start; | ||||
| 
 | ||||
| 	if (address > vma->vm_end) | ||||
| 		return NOPAGE_SIGBUS; /* Disallow mremap */ | ||||
| 	if (!buf) | ||||
| 		return NOPAGE_OOM; | ||||
| 
 | ||||
| 	page = vmalloc_to_page(buf->start + offset); | ||||
| 	if (!page) | ||||
| 		return NOPAGE_OOM; | ||||
| 	get_page(page); | ||||
| 
 | ||||
| 	if (type) | ||||
| 		*type = VM_FAULT_MINOR; | ||||
| 
 | ||||
| 	return page; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * vm_ops for relay file mappings. | ||||
|  */ | ||||
| static struct vm_operations_struct relay_file_mmap_ops = { | ||||
| 	.nopage = relay_buf_nopage, | ||||
| 	.close = relay_file_mmap_close, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_mmap_buf: - mmap channel buffer to process address space | ||||
|  *	@buf: relay channel buffer | ||||
|  *	@vma: vm_area_struct describing memory to be mapped | ||||
|  * | ||||
|  *	Returns 0 if ok, negative on error | ||||
|  * | ||||
|  *	Caller should already have grabbed mmap_sem. | ||||
|  */ | ||||
| int relay_mmap_buf(struct rchan_buf *buf, struct vm_area_struct *vma) | ||||
| { | ||||
| 	unsigned long length = vma->vm_end - vma->vm_start; | ||||
| 	struct file *filp = vma->vm_file; | ||||
| 
 | ||||
| 	if (!buf) | ||||
| 		return -EBADF; | ||||
| 
 | ||||
| 	if (length != (unsigned long)buf->chan->alloc_size) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	vma->vm_ops = &relay_file_mmap_ops; | ||||
| 	vma->vm_private_data = buf; | ||||
| 	buf->chan->cb->buf_mapped(buf, filp); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_alloc_buf - allocate a channel buffer | ||||
|  *	@buf: the buffer struct | ||||
|  *	@size: total size of the buffer | ||||
|  * | ||||
|  *	Returns a pointer to the resulting buffer, NULL if unsuccessful | ||||
|  */ | ||||
| static void *relay_alloc_buf(struct rchan_buf *buf, unsigned long size) | ||||
| { | ||||
| 	void *mem; | ||||
| 	unsigned int i, j, n_pages; | ||||
| 
 | ||||
| 	size = PAGE_ALIGN(size); | ||||
| 	n_pages = size >> PAGE_SHIFT; | ||||
| 
 | ||||
| 	buf->page_array = kcalloc(n_pages, sizeof(struct page *), GFP_KERNEL); | ||||
| 	if (!buf->page_array) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	for (i = 0; i < n_pages; i++) { | ||||
| 		buf->page_array[i] = alloc_page(GFP_KERNEL); | ||||
| 		if (unlikely(!buf->page_array[i])) | ||||
| 			goto depopulate; | ||||
| 	} | ||||
| 	mem = vmap(buf->page_array, n_pages, VM_MAP, PAGE_KERNEL); | ||||
| 	if (!mem) | ||||
| 		goto depopulate; | ||||
| 
 | ||||
| 	memset(mem, 0, size); | ||||
| 	buf->page_count = n_pages; | ||||
| 	return mem; | ||||
| 
 | ||||
| depopulate: | ||||
| 	for (j = 0; j < i; j++) | ||||
| 		__free_page(buf->page_array[j]); | ||||
| 	kfree(buf->page_array); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_create_buf - allocate and initialize a channel buffer | ||||
|  *	@alloc_size: size of the buffer to allocate | ||||
|  *	@n_subbufs: number of sub-buffers in the channel | ||||
|  * | ||||
|  *	Returns channel buffer if successful, NULL otherwise | ||||
|  */ | ||||
| struct rchan_buf *relay_create_buf(struct rchan *chan) | ||||
| { | ||||
| 	struct rchan_buf *buf = kcalloc(1, sizeof(struct rchan_buf), GFP_KERNEL); | ||||
| 	if (!buf) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	buf->padding = kmalloc(chan->n_subbufs * sizeof(size_t *), GFP_KERNEL); | ||||
| 	if (!buf->padding) | ||||
| 		goto free_buf; | ||||
| 
 | ||||
| 	buf->start = relay_alloc_buf(buf, chan->alloc_size); | ||||
| 	if (!buf->start) | ||||
| 		goto free_buf; | ||||
| 
 | ||||
| 	buf->chan = chan; | ||||
| 	kref_get(&buf->chan->kref); | ||||
| 	return buf; | ||||
| 
 | ||||
| free_buf: | ||||
| 	kfree(buf->padding); | ||||
| 	kfree(buf); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_destroy_channel - free the channel struct | ||||
|  * | ||||
|  *	Should only be called from kref_put(). | ||||
|  */ | ||||
| void relay_destroy_channel(struct kref *kref) | ||||
| { | ||||
| 	struct rchan *chan = container_of(kref, struct rchan, kref); | ||||
| 	kfree(chan); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_destroy_buf - destroy an rchan_buf struct and associated buffer | ||||
|  *	@buf: the buffer struct | ||||
|  */ | ||||
| void relay_destroy_buf(struct rchan_buf *buf) | ||||
| { | ||||
| 	struct rchan *chan = buf->chan; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	if (likely(buf->start)) { | ||||
| 		vunmap(buf->start); | ||||
| 		for (i = 0; i < buf->page_count; i++) | ||||
| 			__free_page(buf->page_array[i]); | ||||
| 		kfree(buf->page_array); | ||||
| 	} | ||||
| 	kfree(buf->padding); | ||||
| 	kfree(buf); | ||||
| 	kref_put(&chan->kref, relay_destroy_channel); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_remove_buf - remove a channel buffer | ||||
|  * | ||||
|  *	Removes the file from the fileystem, which also frees the | ||||
|  *	rchan_buf_struct and the channel buffer.  Should only be called from | ||||
|  *	kref_put(). | ||||
|  */ | ||||
| void relay_remove_buf(struct kref *kref) | ||||
| { | ||||
| 	struct rchan_buf *buf = container_of(kref, struct rchan_buf, kref); | ||||
| 	buf->chan->cb->remove_buf_file(buf->dentry); | ||||
| 	relay_destroy_buf(buf); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_buf_empty - boolean, is the channel buffer empty? | ||||
|  *	@buf: channel buffer | ||||
|  * | ||||
|  *	Returns 1 if the buffer is empty, 0 otherwise. | ||||
|  */ | ||||
| int relay_buf_empty(struct rchan_buf *buf) | ||||
| { | ||||
| 	return (buf->subbufs_produced - buf->subbufs_consumed) ? 0 : 1; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_buf_empty); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_buf_full - boolean, is the channel buffer full? | ||||
|  *	@buf: channel buffer | ||||
|  * | ||||
|  *	Returns 1 if the buffer is full, 0 otherwise. | ||||
|  */ | ||||
| int relay_buf_full(struct rchan_buf *buf) | ||||
| { | ||||
| 	size_t ready = buf->subbufs_produced - buf->subbufs_consumed; | ||||
| 	return (ready >= buf->chan->n_subbufs) ? 1 : 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_buf_full); | ||||
| 
 | ||||
| /*
 | ||||
|  * High-level relay kernel API and associated functions. | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * rchan_callback implementations defining default channel behavior.  Used | ||||
|  * in place of corresponding NULL values in client callback struct. | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * subbuf_start() default callback.  Does nothing. | ||||
|  */ | ||||
| static int subbuf_start_default_callback (struct rchan_buf *buf, | ||||
| 					  void *subbuf, | ||||
| 					  void *prev_subbuf, | ||||
| 					  size_t prev_padding) | ||||
| { | ||||
| 	if (relay_buf_full(buf)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * buf_mapped() default callback.  Does nothing. | ||||
|  */ | ||||
| static void buf_mapped_default_callback(struct rchan_buf *buf, | ||||
| 					struct file *filp) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * buf_unmapped() default callback.  Does nothing. | ||||
|  */ | ||||
| static void buf_unmapped_default_callback(struct rchan_buf *buf, | ||||
| 					  struct file *filp) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * create_buf_file_create() default callback.  Does nothing. | ||||
|  */ | ||||
| static struct dentry *create_buf_file_default_callback(const char *filename, | ||||
| 						       struct dentry *parent, | ||||
| 						       int mode, | ||||
| 						       struct rchan_buf *buf, | ||||
| 						       int *is_global) | ||||
| { | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * remove_buf_file() default callback.  Does nothing. | ||||
|  */ | ||||
| static int remove_buf_file_default_callback(struct dentry *dentry) | ||||
| { | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| /* relay channel default callbacks */ | ||||
| static struct rchan_callbacks default_channel_callbacks = { | ||||
| 	.subbuf_start = subbuf_start_default_callback, | ||||
| 	.buf_mapped = buf_mapped_default_callback, | ||||
| 	.buf_unmapped = buf_unmapped_default_callback, | ||||
| 	.create_buf_file = create_buf_file_default_callback, | ||||
| 	.remove_buf_file = remove_buf_file_default_callback, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  *	wakeup_readers - wake up readers waiting on a channel | ||||
|  *	@private: the channel buffer | ||||
|  * | ||||
|  *	This is the work function used to defer reader waking.  The | ||||
|  *	reason waking is deferred is that calling directly from write | ||||
|  *	causes problems if you're writing from say the scheduler. | ||||
|  */ | ||||
| static void wakeup_readers(void *private) | ||||
| { | ||||
| 	struct rchan_buf *buf = private; | ||||
| 	wake_up_interruptible(&buf->read_wait); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	__relay_reset - reset a channel buffer | ||||
|  *	@buf: the channel buffer | ||||
|  *	@init: 1 if this is a first-time initialization | ||||
|  * | ||||
|  *	See relay_reset for description of effect. | ||||
|  */ | ||||
| static inline void __relay_reset(struct rchan_buf *buf, unsigned int init) | ||||
| { | ||||
| 	size_t i; | ||||
| 
 | ||||
| 	if (init) { | ||||
| 		init_waitqueue_head(&buf->read_wait); | ||||
| 		kref_init(&buf->kref); | ||||
| 		INIT_WORK(&buf->wake_readers, NULL, NULL); | ||||
| 	} else { | ||||
| 		cancel_delayed_work(&buf->wake_readers); | ||||
| 		flush_scheduled_work(); | ||||
| 	} | ||||
| 
 | ||||
| 	buf->subbufs_produced = 0; | ||||
| 	buf->subbufs_consumed = 0; | ||||
| 	buf->bytes_consumed = 0; | ||||
| 	buf->finalized = 0; | ||||
| 	buf->data = buf->start; | ||||
| 	buf->offset = 0; | ||||
| 
 | ||||
| 	for (i = 0; i < buf->chan->n_subbufs; i++) | ||||
| 		buf->padding[i] = 0; | ||||
| 
 | ||||
| 	buf->chan->cb->subbuf_start(buf, buf->data, NULL, 0); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_reset - reset the channel | ||||
|  *	@chan: the channel | ||||
|  * | ||||
|  *	This has the effect of erasing all data from all channel buffers | ||||
|  *	and restarting the channel in its initial state.  The buffers | ||||
|  *	are not freed, so any mappings are still in effect. | ||||
|  * | ||||
|  *	NOTE: Care should be taken that the channel isn't actually | ||||
|  *	being used by anything when this call is made. | ||||
|  */ | ||||
| void relay_reset(struct rchan *chan) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan_buf *prev = NULL; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i] || chan->buf[i] == prev) | ||||
| 			break; | ||||
| 		__relay_reset(chan->buf[i], 0); | ||||
| 		prev = chan->buf[i]; | ||||
| 	} | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_reset); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_open_buf - create a new relay channel buffer | ||||
|  * | ||||
|  *	Internal - used by relay_open(). | ||||
|  */ | ||||
| static struct rchan_buf *relay_open_buf(struct rchan *chan, | ||||
| 					const char *filename, | ||||
| 					struct dentry *parent, | ||||
| 					int *is_global) | ||||
| { | ||||
| 	struct rchan_buf *buf; | ||||
| 	struct dentry *dentry; | ||||
| 
 | ||||
| 	if (*is_global) | ||||
| 		return chan->buf[0]; | ||||
| 
 | ||||
| 	buf = relay_create_buf(chan); | ||||
| 	if (!buf) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	/* Create file in fs */ | ||||
| 	dentry = chan->cb->create_buf_file(filename, parent, S_IRUSR, | ||||
| 					   buf, is_global); | ||||
| 	if (!dentry) { | ||||
| 		relay_destroy_buf(buf); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	buf->dentry = dentry; | ||||
| 	__relay_reset(buf, 1); | ||||
| 
 | ||||
| 	return buf; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_close_buf - close a channel buffer | ||||
|  *	@buf: channel buffer | ||||
|  * | ||||
|  *	Marks the buffer finalized and restores the default callbacks. | ||||
|  *	The channel buffer and channel buffer data structure are then freed | ||||
|  *	automatically when the last reference is given up. | ||||
|  */ | ||||
| static inline void relay_close_buf(struct rchan_buf *buf) | ||||
| { | ||||
| 	buf->finalized = 1; | ||||
| 	cancel_delayed_work(&buf->wake_readers); | ||||
| 	flush_scheduled_work(); | ||||
| 	kref_put(&buf->kref, relay_remove_buf); | ||||
| } | ||||
| 
 | ||||
| static inline void setup_callbacks(struct rchan *chan, | ||||
| 				   struct rchan_callbacks *cb) | ||||
| { | ||||
| 	if (!cb) { | ||||
| 		chan->cb = &default_channel_callbacks; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!cb->subbuf_start) | ||||
| 		cb->subbuf_start = subbuf_start_default_callback; | ||||
| 	if (!cb->buf_mapped) | ||||
| 		cb->buf_mapped = buf_mapped_default_callback; | ||||
| 	if (!cb->buf_unmapped) | ||||
| 		cb->buf_unmapped = buf_unmapped_default_callback; | ||||
| 	if (!cb->create_buf_file) | ||||
| 		cb->create_buf_file = create_buf_file_default_callback; | ||||
| 	if (!cb->remove_buf_file) | ||||
| 		cb->remove_buf_file = remove_buf_file_default_callback; | ||||
| 	chan->cb = cb; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_open - create a new relay channel | ||||
|  *	@base_filename: base name of files to create | ||||
|  *	@parent: dentry of parent directory, NULL for root directory | ||||
|  *	@subbuf_size: size of sub-buffers | ||||
|  *	@n_subbufs: number of sub-buffers | ||||
|  *	@cb: client callback functions | ||||
|  * | ||||
|  *	Returns channel pointer if successful, NULL otherwise. | ||||
|  * | ||||
|  *	Creates a channel buffer for each cpu using the sizes and | ||||
|  *	attributes specified.  The created channel buffer files | ||||
|  *	will be named base_filename0...base_filenameN-1.  File | ||||
|  *	permissions will be S_IRUSR. | ||||
|  */ | ||||
| struct rchan *relay_open(const char *base_filename, | ||||
| 			 struct dentry *parent, | ||||
| 			 size_t subbuf_size, | ||||
| 			 size_t n_subbufs, | ||||
| 			 struct rchan_callbacks *cb) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan *chan; | ||||
| 	char *tmpname; | ||||
| 	int is_global = 0; | ||||
| 
 | ||||
| 	if (!base_filename) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	if (!(subbuf_size && n_subbufs)) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	chan = kcalloc(1, sizeof(struct rchan), GFP_KERNEL); | ||||
| 	if (!chan) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	chan->version = RELAYFS_CHANNEL_VERSION; | ||||
| 	chan->n_subbufs = n_subbufs; | ||||
| 	chan->subbuf_size = subbuf_size; | ||||
| 	chan->alloc_size = FIX_SIZE(subbuf_size * n_subbufs); | ||||
| 	setup_callbacks(chan, cb); | ||||
| 	kref_init(&chan->kref); | ||||
| 
 | ||||
| 	tmpname = kmalloc(NAME_MAX + 1, GFP_KERNEL); | ||||
| 	if (!tmpname) | ||||
| 		goto free_chan; | ||||
| 
 | ||||
| 	for_each_online_cpu(i) { | ||||
| 		sprintf(tmpname, "%s%d", base_filename, i); | ||||
| 		chan->buf[i] = relay_open_buf(chan, tmpname, parent, | ||||
| 					      &is_global); | ||||
| 		if (!chan->buf[i]) | ||||
| 			goto free_bufs; | ||||
| 
 | ||||
| 		chan->buf[i]->cpu = i; | ||||
| 	} | ||||
| 
 | ||||
| 	kfree(tmpname); | ||||
| 	return chan; | ||||
| 
 | ||||
| free_bufs: | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i]) | ||||
| 			break; | ||||
| 		relay_close_buf(chan->buf[i]); | ||||
| 		if (is_global) | ||||
| 			break; | ||||
| 	} | ||||
| 	kfree(tmpname); | ||||
| 
 | ||||
| free_chan: | ||||
| 	kref_put(&chan->kref, relay_destroy_channel); | ||||
| 	return NULL; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_open); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_switch_subbuf - switch to a new sub-buffer | ||||
|  *	@buf: channel buffer | ||||
|  *	@length: size of current event | ||||
|  * | ||||
|  *	Returns either the length passed in or 0 if full. | ||||
|  * | ||||
|  *	Performs sub-buffer-switch tasks such as invoking callbacks, | ||||
|  *	updating padding counts, waking up readers, etc. | ||||
|  */ | ||||
| size_t relay_switch_subbuf(struct rchan_buf *buf, size_t length) | ||||
| { | ||||
| 	void *old, *new; | ||||
| 	size_t old_subbuf, new_subbuf; | ||||
| 
 | ||||
| 	if (unlikely(length > buf->chan->subbuf_size)) | ||||
| 		goto toobig; | ||||
| 
 | ||||
| 	if (buf->offset != buf->chan->subbuf_size + 1) { | ||||
| 		buf->prev_padding = buf->chan->subbuf_size - buf->offset; | ||||
| 		old_subbuf = buf->subbufs_produced % buf->chan->n_subbufs; | ||||
| 		buf->padding[old_subbuf] = buf->prev_padding; | ||||
| 		buf->subbufs_produced++; | ||||
| 		if (waitqueue_active(&buf->read_wait)) { | ||||
| 			PREPARE_WORK(&buf->wake_readers, wakeup_readers, buf); | ||||
| 			schedule_delayed_work(&buf->wake_readers, 1); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	old = buf->data; | ||||
| 	new_subbuf = buf->subbufs_produced % buf->chan->n_subbufs; | ||||
| 	new = buf->start + new_subbuf * buf->chan->subbuf_size; | ||||
| 	buf->offset = 0; | ||||
| 	if (!buf->chan->cb->subbuf_start(buf, new, old, buf->prev_padding)) { | ||||
| 		buf->offset = buf->chan->subbuf_size + 1; | ||||
| 		return 0; | ||||
| 	} | ||||
| 	buf->data = new; | ||||
| 	buf->padding[new_subbuf] = 0; | ||||
| 
 | ||||
| 	if (unlikely(length + buf->offset > buf->chan->subbuf_size)) | ||||
| 		goto toobig; | ||||
| 
 | ||||
| 	return length; | ||||
| 
 | ||||
| toobig: | ||||
| 	buf->chan->last_toobig = length; | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_switch_subbuf); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_subbufs_consumed - update the buffer's sub-buffers-consumed count | ||||
|  *	@chan: the channel | ||||
|  *	@cpu: the cpu associated with the channel buffer to update | ||||
|  *	@subbufs_consumed: number of sub-buffers to add to current buf's count | ||||
|  * | ||||
|  *	Adds to the channel buffer's consumed sub-buffer count. | ||||
|  *	subbufs_consumed should be the number of sub-buffers newly consumed, | ||||
|  *	not the total consumed. | ||||
|  * | ||||
|  *	NOTE: kernel clients don't need to call this function if the channel | ||||
|  *	mode is 'overwrite'. | ||||
|  */ | ||||
| void relay_subbufs_consumed(struct rchan *chan, | ||||
| 			    unsigned int cpu, | ||||
| 			    size_t subbufs_consumed) | ||||
| { | ||||
| 	struct rchan_buf *buf; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (cpu >= NR_CPUS || !chan->buf[cpu]) | ||||
| 		return; | ||||
| 
 | ||||
| 	buf = chan->buf[cpu]; | ||||
| 	buf->subbufs_consumed += subbufs_consumed; | ||||
| 	if (buf->subbufs_consumed > buf->subbufs_produced) | ||||
| 		buf->subbufs_consumed = buf->subbufs_produced; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_subbufs_consumed); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_close - close the channel | ||||
|  *	@chan: the channel | ||||
|  * | ||||
|  *	Closes all channel buffers and frees the channel. | ||||
|  */ | ||||
| void relay_close(struct rchan *chan) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan_buf *prev = NULL; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i] || chan->buf[i] == prev) | ||||
| 			break; | ||||
| 		relay_close_buf(chan->buf[i]); | ||||
| 		prev = chan->buf[i]; | ||||
| 	} | ||||
| 
 | ||||
| 	if (chan->last_toobig) | ||||
| 		printk(KERN_WARNING "relay: one or more items not logged " | ||||
| 		       "[item size (%Zd) > sub-buffer size (%Zd)]\n", | ||||
| 		       chan->last_toobig, chan->subbuf_size); | ||||
| 
 | ||||
| 	kref_put(&chan->kref, relay_destroy_channel); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_close); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_flush - close the channel | ||||
|  *	@chan: the channel | ||||
|  * | ||||
|  *	Flushes all channel buffers i.e. forces buffer switch. | ||||
|  */ | ||||
| void relay_flush(struct rchan *chan) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	struct rchan_buf *prev = NULL; | ||||
| 
 | ||||
| 	if (!chan) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < NR_CPUS; i++) { | ||||
| 		if (!chan->buf[i] || chan->buf[i] == prev) | ||||
| 			break; | ||||
| 		relay_switch_subbuf(chan->buf[i], 0); | ||||
| 		prev = chan->buf[i]; | ||||
| 	} | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(relay_flush); | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_open - open file op for relay files | ||||
|  *	@inode: the inode | ||||
|  *	@filp: the file | ||||
|  * | ||||
|  *	Increments the channel buffer refcount. | ||||
|  */ | ||||
| static int relay_file_open(struct inode *inode, struct file *filp) | ||||
| { | ||||
| 	struct rchan_buf *buf = inode->u.generic_ip; | ||||
| 	kref_get(&buf->kref); | ||||
| 	filp->private_data = buf; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_mmap - mmap file op for relay files | ||||
|  *	@filp: the file | ||||
|  *	@vma: the vma describing what to map | ||||
|  * | ||||
|  *	Calls upon relay_mmap_buf to map the file into user space. | ||||
|  */ | ||||
| static int relay_file_mmap(struct file *filp, struct vm_area_struct *vma) | ||||
| { | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 	return relay_mmap_buf(buf, vma); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_poll - poll file op for relay files | ||||
|  *	@filp: the file | ||||
|  *	@wait: poll table | ||||
|  * | ||||
|  *	Poll implemention. | ||||
|  */ | ||||
| static unsigned int relay_file_poll(struct file *filp, poll_table *wait) | ||||
| { | ||||
| 	unsigned int mask = 0; | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 
 | ||||
| 	if (buf->finalized) | ||||
| 		return POLLERR; | ||||
| 
 | ||||
| 	if (filp->f_mode & FMODE_READ) { | ||||
| 		poll_wait(filp, &buf->read_wait, wait); | ||||
| 		if (!relay_buf_empty(buf)) | ||||
| 			mask |= POLLIN | POLLRDNORM; | ||||
| 	} | ||||
| 
 | ||||
| 	return mask; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_release - release file op for relay files | ||||
|  *	@inode: the inode | ||||
|  *	@filp: the file | ||||
|  * | ||||
|  *	Decrements the channel refcount, as the filesystem is | ||||
|  *	no longer using it. | ||||
|  */ | ||||
| static int relay_file_release(struct inode *inode, struct file *filp) | ||||
| { | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 	kref_put(&buf->kref, relay_remove_buf); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_consume - update the consumed count for the buffer | ||||
|  */ | ||||
| static void relay_file_read_consume(struct rchan_buf *buf, | ||||
| 				    size_t read_pos, | ||||
| 				    size_t bytes_consumed) | ||||
| { | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 	size_t read_subbuf; | ||||
| 
 | ||||
| 	if (buf->bytes_consumed + bytes_consumed > subbuf_size) { | ||||
| 		relay_subbufs_consumed(buf->chan, buf->cpu, 1); | ||||
| 		buf->bytes_consumed = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	buf->bytes_consumed += bytes_consumed; | ||||
| 	read_subbuf = read_pos / buf->chan->subbuf_size; | ||||
| 	if (buf->bytes_consumed + buf->padding[read_subbuf] == subbuf_size) { | ||||
| 		if ((read_subbuf == buf->subbufs_produced % n_subbufs) && | ||||
| 		    (buf->offset == subbuf_size)) | ||||
| 			return; | ||||
| 		relay_subbufs_consumed(buf->chan, buf->cpu, 1); | ||||
| 		buf->bytes_consumed = 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_avail - boolean, are there unconsumed bytes available? | ||||
|  */ | ||||
| static int relay_file_read_avail(struct rchan_buf *buf, size_t read_pos) | ||||
| { | ||||
| 	size_t bytes_produced, bytes_consumed, write_offset; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 	size_t produced = buf->subbufs_produced % n_subbufs; | ||||
| 	size_t consumed = buf->subbufs_consumed % n_subbufs; | ||||
| 
 | ||||
| 	write_offset = buf->offset > subbuf_size ? subbuf_size : buf->offset; | ||||
| 
 | ||||
| 	if (consumed > produced) { | ||||
| 		if ((produced > n_subbufs) && | ||||
| 		    (produced + n_subbufs - consumed <= n_subbufs)) | ||||
| 			produced += n_subbufs; | ||||
| 	} else if (consumed == produced) { | ||||
| 		if (buf->offset > subbuf_size) { | ||||
| 			produced += n_subbufs; | ||||
| 			if (buf->subbufs_produced == buf->subbufs_consumed) | ||||
| 				consumed += n_subbufs; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (buf->offset > subbuf_size) | ||||
| 		bytes_produced = (produced - 1) * subbuf_size + write_offset; | ||||
| 	else | ||||
| 		bytes_produced = produced * subbuf_size + write_offset; | ||||
| 	bytes_consumed = consumed * subbuf_size + buf->bytes_consumed; | ||||
| 
 | ||||
| 	if (bytes_produced == bytes_consumed) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	relay_file_read_consume(buf, read_pos, 0); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_subbuf_avail - return bytes available in sub-buffer | ||||
|  */ | ||||
| static size_t relay_file_read_subbuf_avail(size_t read_pos, | ||||
| 					   struct rchan_buf *buf) | ||||
| { | ||||
| 	size_t padding, avail = 0; | ||||
| 	size_t read_subbuf, read_offset, write_subbuf, write_offset; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 
 | ||||
| 	write_subbuf = (buf->data - buf->start) / subbuf_size; | ||||
| 	write_offset = buf->offset > subbuf_size ? subbuf_size : buf->offset; | ||||
| 	read_subbuf = read_pos / subbuf_size; | ||||
| 	read_offset = read_pos % subbuf_size; | ||||
| 	padding = buf->padding[read_subbuf]; | ||||
| 
 | ||||
| 	if (read_subbuf == write_subbuf) { | ||||
| 		if (read_offset + padding < write_offset) | ||||
| 			avail = write_offset - (read_offset + padding); | ||||
| 	} else | ||||
| 		avail = (subbuf_size - padding) - read_offset; | ||||
| 
 | ||||
| 	return avail; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_start_pos - find the first available byte to read | ||||
|  * | ||||
|  *	If the read_pos is in the middle of padding, return the | ||||
|  *	position of the first actually available byte, otherwise | ||||
|  *	return the original value. | ||||
|  */ | ||||
| static size_t relay_file_read_start_pos(size_t read_pos, | ||||
| 					struct rchan_buf *buf) | ||||
| { | ||||
| 	size_t read_subbuf, padding, padding_start, padding_end; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 
 | ||||
| 	read_subbuf = read_pos / subbuf_size; | ||||
| 	padding = buf->padding[read_subbuf]; | ||||
| 	padding_start = (read_subbuf + 1) * subbuf_size - padding; | ||||
| 	padding_end = (read_subbuf + 1) * subbuf_size; | ||||
| 	if (read_pos >= padding_start && read_pos < padding_end) { | ||||
| 		read_subbuf = (read_subbuf + 1) % n_subbufs; | ||||
| 		read_pos = read_subbuf * subbuf_size; | ||||
| 	} | ||||
| 
 | ||||
| 	return read_pos; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read_end_pos - return the new read position | ||||
|  */ | ||||
| static size_t relay_file_read_end_pos(struct rchan_buf *buf, | ||||
| 				      size_t read_pos, | ||||
| 				      size_t count) | ||||
| { | ||||
| 	size_t read_subbuf, padding, end_pos; | ||||
| 	size_t subbuf_size = buf->chan->subbuf_size; | ||||
| 	size_t n_subbufs = buf->chan->n_subbufs; | ||||
| 
 | ||||
| 	read_subbuf = read_pos / subbuf_size; | ||||
| 	padding = buf->padding[read_subbuf]; | ||||
| 	if (read_pos % subbuf_size + count + padding == subbuf_size) | ||||
| 		end_pos = (read_subbuf + 1) * subbuf_size; | ||||
| 	else | ||||
| 		end_pos = read_pos + count; | ||||
| 	if (end_pos >= subbuf_size * n_subbufs) | ||||
| 		end_pos = 0; | ||||
| 
 | ||||
| 	return end_pos; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  *	relay_file_read - read file op for relay files | ||||
|  *	@filp: the file | ||||
|  *	@buffer: the userspace buffer | ||||
|  *	@count: number of bytes to read | ||||
|  *	@ppos: position to read from | ||||
|  * | ||||
|  *	Reads count bytes or the number of bytes available in the | ||||
|  *	current sub-buffer being read, whichever is smaller. | ||||
|  */ | ||||
| static ssize_t relay_file_read(struct file *filp, | ||||
| 			       char __user *buffer, | ||||
| 			       size_t count, | ||||
| 			       loff_t *ppos) | ||||
| { | ||||
| 	struct rchan_buf *buf = filp->private_data; | ||||
| 	struct inode *inode = filp->f_dentry->d_inode; | ||||
| 	size_t read_start, avail; | ||||
| 	ssize_t ret = 0; | ||||
| 	void *from; | ||||
| 
 | ||||
| 	mutex_lock(&inode->i_mutex); | ||||
| 	if(!relay_file_read_avail(buf, *ppos)) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	read_start = relay_file_read_start_pos(*ppos, buf); | ||||
| 	avail = relay_file_read_subbuf_avail(read_start, buf); | ||||
| 	if (!avail) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	from = buf->start + read_start; | ||||
| 	ret = count = min(count, avail); | ||||
| 	if (copy_to_user(buffer, from, count)) { | ||||
| 		ret = -EFAULT; | ||||
| 		goto out; | ||||
| 	} | ||||
| 	relay_file_read_consume(buf, read_start, count); | ||||
| 	*ppos = relay_file_read_end_pos(buf, read_start, count); | ||||
| out: | ||||
| 	mutex_unlock(&inode->i_mutex); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| struct file_operations relay_file_operations = { | ||||
| 	.open		= relay_file_open, | ||||
| 	.poll		= relay_file_poll, | ||||
| 	.mmap		= relay_file_mmap, | ||||
| 	.read		= relay_file_read, | ||||
| 	.llseek		= no_llseek, | ||||
| 	.release	= relay_file_release, | ||||
| }; | ||||
| EXPORT_SYMBOL_GPL(relay_file_operations); | ||||
		Loading…
	
		Reference in a new issue
	
	 Jens Axboe
						Jens Axboe