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