|  | /* | 
|  | * 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); | 
|  | } |