diff --git a/buffers/Makefile b/buffers/Makefile index a42d21ea9fb6d5041034e1a983a9ef19ca7ee410..353fb06860b495fb4881be54aa4166e932f0f842 100644 --- a/buffers/Makefile +++ b/buffers/Makefile @@ -3,6 +3,7 @@ LINUX ?= /lib/modules/$(shell uname -r)/build EXTRA_CFLAGS += -I$(obj)/../include/ # zio-buf-kmalloc.o is now part of zio-core +obj-m = zio-buf-vmalloc.o all: $(MAKE) -C $(LINUX) M=$(shell /bin/pwd) modules diff --git a/buffers/zio-buf-vmalloc.c b/buffers/zio-buf-vmalloc.c new file mode 100644 index 0000000000000000000000000000000000000000..6e1976d889e8f53eda9e5ea8d4b5fb0b1715e173 --- /dev/null +++ b/buffers/zio-buf-vmalloc.c @@ -0,0 +1,378 @@ +/* Alessandro Rubini for CERN, 2012, GNU GPLv2 or later */ + +/* + * This is a vmalloc-based buffer for the ZIO framework. It supports + * mmap from user space and can be used as basis for dma-capable I/O. + * The prefix of all local code/data is till "zbk_" so it's easier + * for our users to use "diff" among the two implementations and see + * what changes. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <linux/zio.h> +#include <linux/zio-buffer.h> +#include <linux/zio-trigger.h> + +/* + * We export a linear buffer to user space, for a single mmap call. + * This is a circular buffer implementation, where data blocks are stuck + * one near the other + */ +struct zbk_instance { + struct zio_bi bi; + struct spinlock lock; + struct list_head list; /* item list */ + /* head and tail are offsets, as tail goes to ctrl->data_offset */ + unsigned long head, tail; + void *data; + unsigned long size; +}; +#define to_zbki(bi) container_of(bi, struct zbk_instance, bi) + +static struct kmem_cache *zbk_slab; + + +/* The list in the structure above collects a bunch of these */ +struct zbk_item { + struct zio_block block; + struct list_head list; /* item list */ + struct zbk_instance *instance; +}; +#define to_item(block) container_of(block, struct zbk_item, block); + +static DEFINE_ZATTR_STD(ZBUF, zbk_std_zattr) = { + ZATTR_REG(zbuf, ZATTR_ZBUF_MAXKB, S_IRUGO | S_IWUGO, 0x0, 128 /* kB */), +}; + +static int zbk_conf_set(struct kobject *kobj, struct zio_attribute *zattr, + uint32_t usr_val) +{ + if (0) { + zattr->value = usr_val; + } else { + /* Temporarily, until I keep track of active maps */ + return -EBUSY; + } + return 0; +} +struct zio_sysfs_operations zbk_sysfs_ops = { + .conf_set = zbk_conf_set, +}; + +/* Simple circular-buffer allocator for data */ +static inline long zbk_alloc_data(struct zbk_instance *zbki, size_t size) +{ + long res; + unsigned long next; + + spin_lock(&zbki->lock); + res = zbki->head; /* most likely */ + next = zbki->head + size; + + if (unlikely(next > zbki->size)) { + /* wrap */ + if (unlikely(zbki->tail < size)) + goto out_oom; + res = 0; + zbki->head = size; + goto out; + } + if (unlikely(zbki->head < zbki->tail)) { + if (unlikely(next > zbki->tail)) + goto out_oom; + zbki->head = next; + goto out; + } + /* easy case */ + zbki->head = next; +out: + spin_unlock(&zbki->lock); + return res; +out_oom: + spin_unlock(&zbki->lock); + return -1; +} + +static inline void zbk_free_data(struct zbk_instance *zbki, long offset, + size_t size) +{ + spin_lock(&zbki->lock); + if (unlikely(offset == 0)) + zbki->tail = size; + else + zbki->tail += size; + spin_unlock(&zbki->lock); +} + +/* Alloc is called by the trigger (for input) or by f->write (for output) */ +static struct zio_block *zbk_alloc_block(struct zio_bi *bi, + struct zio_control *ctrl, + size_t datalen, gfp_t gfp) +{ + struct zbk_instance *zbki = to_zbki(bi); + struct zbk_item *item; + long offset; + + pr_debug("%s:%d\n", __func__, __LINE__); + + /* alloc item and data. Control remains null at this point */ + item = kmem_cache_alloc(zbk_slab, gfp); + offset = zbk_alloc_data(zbki, datalen); + if (!item || offset < 0) + goto out_free; + memset(item, 0, sizeof(*item)); + item->block.data = zbki->data + offset; + item->block.datalen = datalen; + item->instance = zbki; + + ctrl->mem_offset = offset; + zio_set_ctrl(&item->block, ctrl); + + return &item->block; + +out_free: + kmem_cache_free(zbk_slab, item); + return ERR_PTR(-ENOMEM); +} + +/* Free is called by f->read (for input) or by the trigger (for output) */ +static void zbk_free_block(struct zio_bi *bi, struct zio_block *block) +{ + struct zbk_item *item; + struct zbk_instance *zbki; + struct zio_control *ctrl; + + pr_debug("%s:%d\n", __func__, __LINE__); + ctrl = zio_get_ctrl(block); + item = to_item(block); + zbki = item->instance; + zbk_free_data(zbki, ctrl->mem_offset, item->block.datalen); + zio_free_control(ctrl); + kmem_cache_free(zbk_slab, item); +} + +/* When write() stores the first block, we try pushing it */ +static inline int __try_push(struct zio_ti *ti, struct zio_channel *chan, + struct zio_block *block) +{ + /* chek if trigger is disabled */ + if (unlikely((ti->flags & ZIO_STATUS) == ZIO_DISABLED)) + return 0; + if (ti->t_op->push_block(ti, chan, block) < 0) + return 0; + return 1; +} + +/* Store is called by the trigger (for input) or by f->write (for output) */ +static int zbk_store_block(struct zio_bi *bi, struct zio_block *block) +{ + struct zbk_instance *zbki = to_zbki(bi); + struct zio_channel *chan = bi->chan; + struct zbk_item *item; + int awake = 0, pushed = 0, output; + + pr_debug("%s:%d (%p, %p)\n", __func__, __LINE__, bi, block); + + if (unlikely(!zio_get_ctrl(block))) { + WARN_ON(1); + return -EINVAL; + } + + item = to_item(block); + output = (bi->flags & ZIO_DIR) == ZIO_DIR_OUTPUT; + + /* add to the buffer instance or push to the trigger */ + spin_lock(&zbki->lock); + if (list_empty(&zbki->list)) { + if (unlikely(output)) + pushed = __try_push(chan->cset->ti, chan, block); + else + awake = 1; + } + if (likely(!pushed)) + list_add_tail(&item->list, &zbki->list); + spin_unlock(&zbki->lock); + + /* if input, awake user space */ + if (awake && ((bi->flags & ZIO_DIR) == ZIO_DIR_INPUT)) + wake_up_interruptible(&bi->q); + + return 0; +} + +/* Retr is called by f->read (for input) or by the trigger (for output) */ +static struct zio_block *zbk_retr_block(struct zio_bi *bi) +{ + struct zbk_item *item; + struct zbk_instance *zbki; + struct zio_ti *ti; + struct list_head *first; + int awake = 0; + + zbki = to_zbki(bi); + + spin_lock(&zbki->lock); + if (list_empty(&zbki->list)) + goto out_unlock; + first = zbki->list.next; + item = list_entry(first, struct zbk_item, list); + list_del(&item->list); + awake = 1; + spin_unlock(&zbki->lock); + + if (awake && ((bi->flags & ZIO_DIR) == ZIO_DIR_OUTPUT)) + wake_up_interruptible(&bi->q); + pr_debug("%s:%d (%p, %p)\n", __func__, __LINE__, bi, item); + return &item->block; + +out_unlock: + spin_unlock(&zbki->lock); + /* There is no data in buffer, and we may pull to have data soon */ + ti = bi->cset->ti; + if ((bi->flags & ZIO_DIR) == ZIO_DIR_INPUT && ti->t_op->pull_block){ + /* chek if trigger is disabled */ + if (unlikely((ti->flags & ZIO_STATUS) == ZIO_DISABLED)) + return NULL; + ti->t_op->pull_block(ti, bi->chan); + } + pr_debug("%s:%d (%p, %p)\n", __func__, __LINE__, bi, NULL); + return NULL; +} + +/* Create is called by zio for each channel electing to use this buffer type */ +static struct zio_bi *zbk_create(struct zio_buffer_type *zbuf, + struct zio_channel *chan) +{ + struct zbk_instance *zbki; + size_t size; + + pr_debug("%s:%d\n", __func__, __LINE__); + + zbki = kzalloc(sizeof(*zbki), GFP_KERNEL); + if (!zbki) + return ERR_PTR(-ENOMEM); + size = 1024 * zbuf->zattr_set.std_zattr[ZATTR_ZBUF_MAXKB].value; + zbki->size = size; + zbki->data = vmalloc(size); + if (!zbki->data) { + kfree(zbki); + return ERR_PTR(-ENOMEM); + } + spin_lock_init(&zbki->lock); + INIT_LIST_HEAD(&zbki->list); + + /* all the fields of zio_bi are initialied by the caller */ + return &zbki->bi; +} + +/* destroy is called by zio on channel removal or if it changes buffer type */ +static void zbk_destroy(struct zio_bi *bi) +{ + struct zbk_instance *zbki = to_zbki(bi); + struct zbk_item *item; + struct list_head *pos, *tmp; + + pr_debug("%s:%d\n", __func__, __LINE__); + + /* no need to lock here, zio ensures we are not active */ + list_for_each_safe(pos, tmp, &zbki->list) { + item = list_entry(pos, struct zbk_item, list); + zbk_free_block(&zbki->bi, &item->block); + } + vfree(zbki->data); + kfree(zbki); +} + +static const struct zio_buffer_operations zbk_buffer_ops = { + .alloc_block = zbk_alloc_block, + .free_block = zbk_free_block, + .store_block = zbk_store_block, + .retr_block = zbk_retr_block, + .create = zbk_create, + .destroy = zbk_destroy, +}; + +/* + * To support mmap we implement the vm operations. We'll need + * refcounting later, to safely change the buffer size (which we + * refuse by now) + */ +static int zbk_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct file *f = vma->vm_file; + struct zio_f_priv *priv = f->private_data; + struct zio_bi *bi = priv->chan->bi; + struct zbk_instance *zbki = to_zbki(bi); + long off = vmf->pgoff * PAGE_SIZE; + struct page *p; + void *addr; + + if (priv->type == ZIO_CDEV_CTRL) + return VM_FAULT_SIGBUS; + + printk("fault at %li (size %li)\n", off, zbki->size); + if (off > zbki->size) + return VM_FAULT_SIGBUS; + + addr = zbki->data + off; + printk("%s: uaddr %p, off %li: kaddr %p\n", + __FUNCTION__, vmf->virtual_address, off, addr); + p = vmalloc_to_page(addr); + get_page(p); + vmf->page = p; + return 0; +} + +static struct vm_operations_struct zbk_vma_ops = { + /* FIXME: open and close for refcounting */ + .fault = zbk_fault, +}; + +static struct zio_buffer_type zbk_buffer = { + .owner = THIS_MODULE, + .zattr_set = { + .std_zattr = zbk_std_zattr, + }, + .s_op = &zbk_sysfs_ops, + .b_op = &zbk_buffer_ops, + .v_op = &zbk_vma_ops, + .f_op = &zio_generic_file_operations, +}; + +static int __init zbk_init(void) +{ + int ret; + + /* Can't use "zbk_item" as name and KMEM_CACHE_NAMED is not there */ + zbk_slab = kmem_cache_create("zio-vmalloc", sizeof(struct zbk_item), + __alignof__(struct zbk_item), 0, NULL); + if (!zbk_slab) + return -ENOMEM; + ret = zio_register_buf(&zbk_buffer, "vmalloc"); + if (ret < 0) + kmem_cache_destroy(zbk_slab); + return ret; + +} + +static void __exit zbk_exit(void) +{ + zio_unregister_buf(&zbk_buffer); + kmem_cache_destroy(zbk_slab); +} + +module_init(zbk_init); +module_exit(zbk_exit); +MODULE_AUTHOR("Alessandro Rubini"); +MODULE_LICENSE("GPL"); diff --git a/zio-core.c b/zio-core.c index 9e3d51121aab8fbaf43b6510befe5aa44a5caec7..055c24a8492108dc72a1b59a5b850db355e15424 100644 --- a/zio-core.c +++ b/zio-core.c @@ -32,13 +32,13 @@ struct zio_control *zio_alloc_control(gfp_t gfp) ctrl->flags |= ZIO_CONTROL_LITTLE_ENDIAN; return ctrl; } -EXPORT_SYMBOL(zio_alloc_control); /* used by buffers */ +EXPORT_SYMBOL(zio_alloc_control); void zio_free_control(struct zio_control *ctrl) { kmem_cache_free(zio_ctrl_slab, ctrl); } -EXPORT_SYMBOL(zio_free_control); /* used by buffers */ +EXPORT_SYMBOL(zio_free_control); int __init zio_slab_init(void) {