diff --git a/VERSION b/VERSION index c9eb49ab..93c8e745 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -B1-85-g1aadf91901+1 \ No newline at end of file +B1-86-g6aeafc6651+1 \ No newline at end of file diff --git a/module/Makefile b/module/Makefile index d595cc49..9882c38f 100644 --- a/module/Makefile +++ b/module/Makefile @@ -3,14 +3,20 @@ USER := $(shell whoami) KVER ?= $(shell uname -r) KDIR ?= /lib/modules/$(KVER)/build -all: +all: test make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean -test: all +test: + gcc test.c -Wall -Werror -g -Og -o test + +load: all grep -q '^uio' /proc/modules || sudo modprobe uio grep -q '^kvmfr' /proc/modules && sudo rmmod kvmfr || true sudo insmod ./kvmfr.ko sudo chown $(USER) /dev/uio0 + sudo chown $(USER) /dev/kvmfr0 + +.PHONY: test diff --git a/module/kvmfr.c b/module/kvmfr.c index c4eb296f..ead1819e 100644 --- a/module/kvmfr.c +++ b/module/kvmfr.c @@ -25,73 +25,367 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include +#include +#include #include +#include "kvmfr.h" + +DEFINE_MUTEX(minor_lock); +DEFINE_IDR(kvmfr_idr); + +#define KVMFR_UIO_NAME "KVMFR" +#define KVMFR_UIO_VER "0.0.2" +#define KVMFR_DEV_NAME "kvmfr" +#define KVMFR_MAX_DEVICES 10 + +struct kvmfr_info +{ + int major; + struct class * pClass; +}; + +static struct kvmfr_info *kvmfr; + +struct kvmfr_dev +{ + struct pci_dev * dev; + struct uio_info uio; + int minor; + dev_t devNo; + struct device * pDev; + void * addr; +}; + +struct kvmfrbuf +{ + struct kvmfr_dev * kdev; + pgoff_t pagecount; + struct dev_pagemap * pgmap; + struct page ** pages; +}; + +static vm_fault_t kvmfr_vm_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct kvmfrbuf *kbuf = (struct kvmfrbuf *)vma->vm_private_data; + + vmf->page = kbuf->pages[vmf->pgoff]; + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct kvmfr_vm_ops = +{ + .fault = kvmfr_vm_fault +}; + +static struct sg_table * map_kvmfrbuf(struct dma_buf_attachment *at, + enum dma_data_direction direction) +{ + struct kvmfrbuf *kbuf = at->dmabuf->priv; + struct sg_table *sg; + int ret; + + sg = kzalloc(sizeof(*sg), GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + ret = sg_alloc_table_from_pages(sg, kbuf->pages, kbuf->pagecount, + 0, kbuf->pagecount << PAGE_SHIFT, GFP_KERNEL); + if (ret < 0) + goto err; + + if (!dma_map_sg(at->dev, sg->sgl, sg->nents, direction)) + { + ret = -EINVAL; + goto err; + } + + return sg; + +err: + sg_free_table(sg); + kfree(sg); + return ERR_PTR(ret); +} + +static void unmap_kvmfrbuf(struct dma_buf_attachment * at, struct sg_table * sg, enum dma_data_direction direction) +{ + dma_unmap_sg(at->dev, sg->sgl, sg->nents, direction); + sg_free_table(sg); + kfree(sg); +} + +static void release_kvmfrbuf(struct dma_buf * buf) +{ + struct kvmfrbuf *kbuf = (struct kvmfrbuf *)buf->priv; + pgoff_t pg; + + for (pg = 0; pg < kbuf->pagecount; pg++) + put_page(kbuf->pages[pg]); + + devm_memunmap_pages(&kbuf->kdev->dev->dev, kbuf->pgmap); + kfree(kbuf->pgmap); + kfree(kbuf->pages); + kfree(kbuf); +} + +static void * kmap_kvmfrbuf(struct dma_buf * buf, unsigned long page_num) +{ + struct kvmfrbuf * kbuf = (struct kvmfrbuf *)buf->priv; + struct page * page = kbuf->pages[page_num]; + return kmap(page); +} + +static void kunmap_kvmfrbuf(struct dma_buf * buf, unsigned long page_num, void * vaddr) +{ + kunmap(vaddr); +} + +static int mmap_kvmfrbuf(struct dma_buf * buf, struct vm_area_struct * vma) +{ + if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0) + return -EINVAL; + vma->vm_ops = &kvmfr_vm_ops; + vma->vm_private_data = buf->priv; + return 0; +} + +static const struct dma_buf_ops kvmfrbuf_ops = +{ + .map_dma_buf = map_kvmfrbuf, + .unmap_dma_buf = unmap_kvmfrbuf, + .release = release_kvmfrbuf, + .map = kmap_kvmfrbuf, + .unmap = kunmap_kvmfrbuf, + .mmap = mmap_kvmfrbuf +}; + +inline static unsigned long get_min_align(void) +{ + if (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP)) + return PAGES_PER_SUBSECTION; + else + return PAGES_PER_SECTION; +} + +static long kvmfr_dmabuf_create(struct kvmfr_dev * kdev, struct file * filp, unsigned long arg) +{ + const unsigned long min_align = get_min_align(); + struct kvmfr_dmabuf_create create; + DEFINE_DMA_BUF_EXPORT_INFO(exp_kdev); + struct kvmfrbuf * kbuf; + struct dma_buf * buf; + u32 i; + u8 *p; + int ret = -EINVAL; + + if (copy_from_user(&create, (void __user *)arg, + sizeof(create))) + return -EFAULT; + + if (!IS_ALIGNED(create.offset, min_align) || + !IS_ALIGNED(create.size , min_align)) + { + printk("kvmfr: buffer not aligned to 0x%lx bytes", min_align << PAGE_SHIFT); + return -EINVAL; + } + + if (create.offset + create.size > pci_resource_len(kdev->dev, 2)) + return -EINVAL; + + kbuf = kzalloc(sizeof(struct kvmfrbuf), GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + kbuf->kdev = kdev; + kbuf->pagecount = (create.size - create.offset) >> PAGE_SHIFT; + + kbuf->pgmap = kzalloc(sizeof(*kbuf->pgmap), GFP_KERNEL); + if (!kbuf->pgmap) + { + ret = -ENOMEM; + goto err; + } + + kbuf->pgmap->res.start = pci_resource_start(kdev->dev, 2) + create.offset; + kbuf->pgmap->res.end = kbuf->pgmap->res.start + create.size - 1; + kbuf->pgmap->res.flags = pci_resource_flags(kdev->dev, 2); + kbuf->pgmap->type = MEMORY_DEVICE_DEVDAX; + + kdev->addr = devm_memremap_pages(&kdev->dev->dev, kbuf->pgmap); + if (IS_ERR(kdev->addr)) + { + ret = PTR_ERR(kdev->addr); + goto err; + } + + kbuf->pages = kmalloc_array(kbuf->pagecount, sizeof(*kbuf->pages), GFP_KERNEL); + if (!kbuf->pages) + { + ret = -ENOMEM; + goto err_unmap; + } + + p = kdev->addr; + for(i = 0; i < kbuf->pagecount; ++i) + { + kbuf->pages[i] = virt_to_page(p); + p += PAGE_SIZE; + } + + exp_kdev.ops = &kvmfrbuf_ops; + exp_kdev.size = create.size; + exp_kdev.priv = kbuf; + exp_kdev.flags = O_RDWR; + + buf = dma_buf_export(&exp_kdev); + if (IS_ERR(buf)) + { + ret = PTR_ERR(buf); + goto err; + } + + printk("kvmfr_dmabuf_create: offset: %llu, size: %llu\n", create.offset, create.size); + return dma_buf_fd(buf, create.flags & KVMFR_DMABUF_FLAG_CLOEXEC ? O_CLOEXEC : 0); + +err_unmap: + devm_memunmap_pages(&kdev->dev->dev, kbuf->pgmap); +err: + kfree(kbuf->pgmap); + kfree(kbuf->pages); + kfree(kbuf); + return ret; +} + +static long device_ioctl(struct file * filp, unsigned int ioctl, unsigned long arg) +{ + struct kvmfr_dev * kdev; + long ret; + + kdev = (struct kvmfr_dev *)idr_find(&kvmfr_idr, iminor(filp->f_inode)); + if (!kdev) + return -EINVAL; + + switch(ioctl) + { + case KVMFR_DMABUF_CREATE: + ret = kvmfr_dmabuf_create(kdev, filp, arg); + break; + + case KVMFR_DMABUF_GETALIGN: + ret = get_min_align() << PAGE_SHIFT; + break; + + case KVMFR_DMABUF_GETSIZE: + ret = pci_resource_len(kdev->dev, 2); + break; + + default: + return -ENOTTY; + } + + return ret; +} + +static struct file_operations fops = +{ + .owner = THIS_MODULE, + .unlocked_ioctl = device_ioctl +}; + static int kvmfr_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { - struct uio_info *info; + struct kvmfr_dev *kdev; - info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); - if (!info) + kdev = kzalloc(sizeof(struct kvmfr_dev), GFP_KERNEL); + if (!kdev) return -ENOMEM; + kdev->dev = dev; + if (pci_enable_device(dev)) goto out_free; - if (pci_request_regions(dev, "kvmfr")) + if (pci_request_regions(dev, KVMFR_DEV_NAME)) goto out_disable; - info->mem[0].addr = pci_resource_start(dev, 2); - if (!info->mem[0].addr) + kdev->uio.mem[0].addr = pci_resource_start(dev, 2); + if (!kdev->uio.mem[0].addr) goto out_release; - info->mem[0].internal_addr = ioremap_wt( + kdev->uio.mem[0].internal_addr = ioremap_wt( pci_resource_start(dev, 2), pci_resource_len (dev, 2) ); - if (!info->mem[0].internal_addr) + + if (!kdev->uio.mem[0].internal_addr) goto out_release; - info->mem[0].size = pci_resource_len(dev, 2); - info->mem[0].memtype = UIO_MEM_PHYS; - info->name = "KVMFR"; - info->version = "0.0.1"; - info->irq = 0; - info->irq_flags = 0; - info->handler = NULL; + kdev->uio.mem[0].size = pci_resource_len(dev, 2); + kdev->uio.mem[0].memtype = UIO_MEM_PHYS; + kdev->uio.name = KVMFR_UIO_NAME; + kdev->uio.version = KVMFR_UIO_VER; + kdev->uio.irq = 0; + kdev->uio.irq_flags = 0; + kdev->uio.handler = NULL; - if (uio_register_device(&dev->dev, info)) + if (uio_register_device(&dev->dev, &kdev->uio)) goto out_unmap; - pci_set_drvdata(dev, info); + mutex_lock(&minor_lock); + kdev->minor = idr_alloc(&kvmfr_idr, kdev, 0, KVMFR_MAX_DEVICES, GFP_KERNEL); + if (kdev->minor < 0) + { + mutex_unlock(&minor_lock); + goto out_unreg; + } + mutex_unlock(&minor_lock); + + kdev->devNo = MKDEV(kvmfr->major, kdev->minor); + kdev->pDev = device_create(kvmfr->pClass, NULL, kdev->devNo, NULL, KVMFR_DEV_NAME "%d", kdev->minor); + if (IS_ERR(kdev->pDev)) + goto out_unminor; + + pci_set_drvdata(dev, kdev); return 0; +out_unminor: + mutex_lock(&minor_lock); + idr_remove(&kvmfr_idr, kdev->minor); + mutex_unlock(&minor_lock); +out_unreg: + uio_unregister_device(&kdev->uio); out_unmap: - printk("unmap\n"); - iounmap(info->mem[0].internal_addr); + iounmap(kdev->uio.mem[0].internal_addr); out_release: - printk("release\n"); pci_release_regions(dev); out_disable: - printk("disable\n"); pci_disable_device(dev); out_free: - printk("free\n"); - kfree(info); + kfree(kdev); return -ENODEV; } static void kvmfr_pci_remove(struct pci_dev *dev) { - struct uio_info *info = pci_get_drvdata(dev); + struct kvmfr_dev *kdev = pci_get_drvdata(dev); - uio_unregister_device(info); + device_destroy(kvmfr->pClass, kdev->devNo); + + mutex_lock(&minor_lock); + idr_remove(&kvmfr_idr, kdev->minor); + mutex_unlock(&minor_lock); + + uio_unregister_device(&kdev->uio); pci_release_regions(dev); pci_disable_device(dev); - iounmap(info->mem[0].internal_addr); + iounmap(kdev->uio.mem[0].internal_addr); - kfree(info); + kfree(kdev); } static struct pci_device_id kvmfr_pci_ids[] = @@ -107,13 +401,57 @@ static struct pci_device_id kvmfr_pci_ids[] = static struct pci_driver kvmfr_pci_driver = { - .name = "kvmfr", + .name = KVMFR_DEV_NAME, .id_table = kvmfr_pci_ids, .probe = kvmfr_pci_probe, .remove = kvmfr_pci_remove }; -module_pci_driver(kvmfr_pci_driver); +static int __init kvmfr_module_init(void) +{ + int ret; + + kvmfr = kzalloc(sizeof(struct kvmfr_info), GFP_KERNEL); + if (!kvmfr) + return -ENOMEM; + + kvmfr->major = register_chrdev(0, KVMFR_DEV_NAME, &fops); + if (kvmfr->major < 0) + goto out_free; + + kvmfr->pClass = class_create(THIS_MODULE, KVMFR_DEV_NAME); + if (IS_ERR(kvmfr->pClass)) + goto out_unreg; + + ret = pci_register_driver(&kvmfr_pci_driver); + if (ret < 0) + goto out_class_destroy; + + return 0; + +out_class_destroy: + class_destroy(kvmfr->pClass); +out_unreg: + unregister_chrdev(kvmfr->major, KVMFR_DEV_NAME); +out_free: + kfree(kvmfr); + + kvmfr = NULL; + return -ENODEV; +} + +static void __exit kvmfr_module_exit(void) +{ + pci_unregister_driver(&kvmfr_pci_driver); + class_destroy(kvmfr->pClass); + unregister_chrdev(kvmfr->major, KVMFR_DEV_NAME); + kfree(kvmfr); + kvmfr = NULL; +} + +module_init(kvmfr_module_init); +module_exit(kvmfr_module_exit); + MODULE_DEVICE_TABLE(pci, kvmfr_pci_ids); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Geoffrey McRae"); \ No newline at end of file diff --git a/module/test.c b/module/test.c new file mode 100644 index 00000000..99eb56ee --- /dev/null +++ b/module/test.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kvmfr.h" + +int main() +{ + int fd = open("/dev/kvmfr0", O_RDWR); + if (fd < 0) + { + perror("open"); + return -1; + } + + unsigned long size = ioctl(fd, KVMFR_DMABUF_GETSIZE , 0); + unsigned long alignment = ioctl(fd, KVMFR_DMABUF_GETALIGN, 0); + + printf("Size: %lu MiB, Alignment: %lu MiB\n", size / 1024 / 1024, alignment / 1024 / 1024); + + struct kvmfr_dmabuf_create create = + { + .flags = KVMFR_DMABUF_FLAG_CLOEXEC, + .offset = 0x0, + .size = size, + }; + int dmaFd = ioctl(fd, KVMFR_DMABUF_CREATE, &create); + if (dmaFd < 0) + { + perror("ioctl"); + return -1; + } + + void * mem = mmap(NULL, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, dmaFd, 0); + if (!mem) + { + perror("mmap"); + return -1; + } + + memset(mem, 0xAA, create.size); + munmap(mem, create.size); + close(fd); + return 0; +} \ No newline at end of file