diff options
author | Geoff Thorpe <Geoff.Thorpe@freescale.com> | 2013-03-30 07:22:34 (GMT) |
---|---|---|
committer | Emil Medve <Emilian.Medve@Freescale.com> | 2013-04-02 09:42:37 (GMT) |
commit | 9ee0932abb9fec9726751ee6c5121c892097f24c (patch) | |
tree | 361a306cd4a93df849785f0236068e223b10e4dd /drivers/misc | |
parent | bcf18f2abbafd6ee3492ec1e7e7e4b3dbd096bff (diff) | |
download | linux-fsl-qoriq-9ee0932abb9fec9726751ee6c5121c892097f24c.tar.xz |
fsl_usdpaa: Add userspace DPAA resource management driver
Signed-off-by: Geoff Thorpe <Geoff.Thorpe@freescale.com>
Signed-off-by: Hai-Ying Wang <Haiying.Wang@freescale.com>
Signed-off-by: Emil Medve <Emilian.Medve@Freescale.com>
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 31 | ||||
-rw-r--r-- | drivers/misc/Makefile | 5 | ||||
-rw-r--r-- | drivers/misc/fsl_usdpaa.c | 701 |
3 files changed, 728 insertions, 9 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b151b7c..0e7d531 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -56,7 +56,7 @@ config ATMEL_PWM depends on HAVE_CLK help This option enables device driver support for the PWM channels - on certain Atmel processors. Pulse Width Modulation is used for + on certain Atmel processors. Pulse Width Modulation is used for purposes including software controlled power-efficient backlights on LCD displays, motor control, and waveform generation. @@ -211,6 +211,23 @@ config ENCLOSURE_SERVICES driver (SCSI/ATA) which supports enclosures or a SCSI enclosure device (SES) to use these services. +config FSL_USDPAA + bool "Freescale USDPAA process driver" + depends on FSL_DPA + default y + help + This driver provides user-space access to kernel-managed + resource interfaces for USDPAA applications, on the assumption + that each process will open this device once. Specifically, this + device exposes functionality that would be awkward if exposed + via the portal devices - ie. this device exposes functionality + that is inherently process-wide rather than portal-specific. + This device is necessary for obtaining access to DMA memory and + for allocation of Qman and Bman resources. In short, if you wish + to use USDPAA applications, you need this. + + If unsure, say Y. + config SGI_XP tristate "Support communication between SGI SSIs" depends on NET @@ -346,14 +363,14 @@ config SENSORS_BH1780 will be called bh1780gli. config SENSORS_BH1770 - tristate "BH1770GLC / SFH7770 combined ALS - Proximity sensor" - depends on I2C - ---help--- - Say Y here if you want to build a driver for BH1770GLC (ROHM) or + tristate "BH1770GLC / SFH7770 combined ALS - Proximity sensor" + depends on I2C + ---help--- + Say Y here if you want to build a driver for BH1770GLC (ROHM) or SFH7770 (Osram) combined ambient light and proximity sensor chip. - To compile this driver as a module, choose M here: the - module will be called bh1770glc. If unsure, say N here. + To compile this driver as a module, choose M here: the + module will be called bh1770glc. If unsure, say N here. config SENSORS_APDS990X tristate "APDS990X combined als and proximity sensors" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 2129377..6a92bc9 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -15,14 +15,15 @@ obj-$(CONFIG_BMP085_I2C) += bmp085-i2c.o obj-$(CONFIG_BMP085_SPI) += bmp085-spi.o obj-$(CONFIG_ICS932S401) += ics932s401.o obj-$(CONFIG_LKDTM) += lkdtm.o -obj-$(CONFIG_TIFM_CORE) += tifm_core.o -obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o +obj-$(CONFIG_TIFM_CORE) += tifm_core.o +obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o +obj-$(CONFIG_FSL_USDPAA) += fsl_usdpaa.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o obj-$(CONFIG_SGI_XP) += sgi-xp/ obj-$(CONFIG_SGI_GRU) += sgi-gru/ diff --git a/drivers/misc/fsl_usdpaa.c b/drivers/misc/fsl_usdpaa.c new file mode 100644 index 0000000..206d3ee --- /dev/null +++ b/drivers/misc/fsl_usdpaa.c @@ -0,0 +1,701 @@ +/* Copyright (C) 2008-2012 Freescale Semiconductor, Inc. + * Authors: Andy Fleming <afleming@freescale.com> + * Timur Tabi <timur@freescale.com> + * Geoff Thorpe <Geoff.Thorpe@freescale.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/fsl_usdpaa.h> +#include <linux/fsl_qman.h> +#include <linux/fsl_bman.h> + +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/mm.h> +#include <linux/of.h> +#include <linux/memblock.h> +#include <linux/slab.h> + +/* Physical address range of the memory reservation, exported for mm/mem.c */ +static u64 phys_start; +static u64 phys_size; +/* PFN versions of the above */ +static unsigned long pfn_start; +static unsigned long pfn_size; + +/* Memory reservations are manipulated under this spinlock (which is why 'refs' + * isn't atomic_t). */ +static DEFINE_SPINLOCK(mem_lock); + +/* The range of TLB1 indices */ +static unsigned int first_tlb; +static unsigned int num_tlb; +static unsigned int current_tlb; /* loops around for fault handling */ + +/* Memory reservation is represented as a list of 'mem_fragment's, some of which + * may be mapped. Unmapped fragments are always merged where possible. */ +static LIST_HEAD(mem_list); + +struct mem_mapping; + +/* Memory fragments are in 'mem_list'. */ +struct mem_fragment { + u64 base; + u64 len; + unsigned long pfn_base; /* PFN version of 'base' */ + unsigned long pfn_len; /* PFN version of 'len' */ + unsigned int refs; /* zero if unmapped */ + struct list_head list; + /* if mapped, flags+name captured at creation time */ + u32 flags; + char name[USDPAA_DMA_NAME_MAX]; + /* support multi-process locks per-memory-fragment. */ + int has_locking; + wait_queue_head_t wq; + struct mem_mapping *owner; +}; + +/* Mappings of memory fragments in 'struct ctx'. These are created from + * ioctl(USDPAA_IOCTL_DMA_MAP), though the actual mapping then happens via a + * mmap(). */ +struct mem_mapping { + struct mem_fragment *frag; + struct list_head list; +}; + +/* Per-FD state (which should also be per-process but we don't enforce that) */ +struct ctx { + /* Allocated resources get put here for accounting */ + struct dpa_alloc ids[usdpaa_id_max]; + struct list_head maps; +}; + +/* Different resource classes */ +static const struct alloc_backend { + enum usdpaa_id_type id_type; + int (*alloc)(u32 *, u32, u32, int); + void (*release)(u32 base, unsigned int count); + const char *acronym; +} alloc_backends[] = { + { + .id_type = usdpaa_id_fqid, + .alloc = qman_alloc_fqid_range, + .release = qman_release_fqid_range, + .acronym = "FQID" + }, + { + .id_type = usdpaa_id_bpid, + .alloc = bman_alloc_bpid_range, + .release = bman_release_bpid_range, + .acronym = "BPID" + }, + { + .id_type = usdpaa_id_qpool, + .alloc = qman_alloc_pool_range, + .release = qman_release_pool_range, + .acronym = "QPOOL" + }, + { + .id_type = usdpaa_id_cgrid, + .alloc = qman_alloc_cgrid_range, + .release = qman_release_cgrid_range, + .acronym = "CGRID" + }, + { + .id_type = usdpaa_id_ceetm0_lfqid, + .alloc = qman_alloc_ceetm0_lfqid_range, + .release = qman_release_ceetm0_lfqid_range, + .acronym = "CEETM0_LFQID" + }, + { + .id_type = usdpaa_id_ceetm0_channelid, + .alloc = qman_alloc_ceetm0_channel_range, + .release = qman_release_ceetm0_channel_range, + .acronym = "CEETM0_CHANNELID" + }, + { + .id_type = usdpaa_id_ceetm1_lfqid, + .alloc = qman_alloc_ceetm1_lfqid_range, + .release = qman_release_ceetm1_lfqid_range, + .acronym = "CEETM1_LFQID" + }, + { + .id_type = usdpaa_id_ceetm1_channelid, + .alloc = qman_alloc_ceetm1_channel_range, + .release = qman_release_ceetm1_channel_range, + .acronym = "CEETM1_CHANNELID" + }, + { + /* This terminates the array */ + .id_type = usdpaa_id_max + } +}; + +/* Helper for ioctl_dma_map() when we have a larger fragment than we need. This + * splits the fragment into 4 and returns the upper-most. (The caller can loop + * until it has a suitable fragment size.) */ +static struct mem_fragment *split_frag(struct mem_fragment *frag) +{ + struct mem_fragment *x[3]; + x[0] = kmalloc(sizeof(struct mem_fragment), GFP_KERNEL); + x[1] = kmalloc(sizeof(struct mem_fragment), GFP_KERNEL); + x[2] = kmalloc(sizeof(struct mem_fragment), GFP_KERNEL); + if (!x[0] || !x[1] || !x[2]) { + kfree(x[0]); + kfree(x[1]); + kfree(x[2]); + return NULL; + } + BUG_ON(frag->refs); + frag->len >>= 2; + frag->pfn_len >>= 2; + x[0]->base = frag->base + frag->len; + x[1]->base = x[0]->base + frag->len; + x[2]->base = x[1]->base + frag->len; + x[0]->len = x[1]->len = x[2]->len = frag->len; + x[0]->pfn_base = frag->pfn_base + frag->pfn_len; + x[1]->pfn_base = x[0]->pfn_base + frag->pfn_len; + x[2]->pfn_base = x[1]->pfn_base + frag->pfn_len; + x[0]->pfn_len = x[1]->pfn_len = x[2]->pfn_len = frag->pfn_len; + x[0]->refs = x[1]->refs = x[2]->refs = 0; + list_add(&x[0]->list, &frag->list); + list_add(&x[1]->list, &x[0]->list); + list_add(&x[2]->list, &x[1]->list); + return x[2]; +} + +/* Conversely, when a fragment is released we look to see whether its + * similarly-split siblings are free to be reassembled. */ +static struct mem_fragment *merge_frag(struct mem_fragment *frag) +{ + /* If this fragment can be merged with its siblings, it will have + * newbase and newlen as its geometry. */ + uint64_t newlen = frag->len << 2; + uint64_t newbase = frag->base & ~(newlen - 1); + struct mem_fragment *tmp, *leftmost = frag, *rightmost = frag; + /* Scan left until we find the start */ + tmp = list_entry(frag->list.prev, struct mem_fragment, list); + while ((&tmp->list != &mem_list) && (tmp->base >= newbase)) { + if (tmp->refs) + return NULL; + if (tmp->len != tmp->len) + return NULL; + leftmost = tmp; + tmp = list_entry(tmp->list.prev, struct mem_fragment, list); + } + /* Scan right until we find the end */ + tmp = list_entry(frag->list.next, struct mem_fragment, list); + while ((&tmp->list != &mem_list) && (tmp->base < (newbase + newlen))) { + if (tmp->refs) + return NULL; + if (tmp->len != tmp->len) + return NULL; + rightmost = tmp; + tmp = list_entry(tmp->list.next, struct mem_fragment, list); + } + if (leftmost == rightmost) + return NULL; + /* OK, we can merge */ + frag = leftmost; + frag->len = newlen; + frag->pfn_len = newlen >> PAGE_SHIFT; + while (1) { + int lastone; + tmp = list_entry(frag->list.next, struct mem_fragment, list); + lastone = (tmp == rightmost); + if (&tmp->list == &mem_list) + break; + list_del(&tmp->list); + kfree(tmp); + if (lastone) + break; + } + return frag; +} + +/* Helper to verify that 'sz' is (4096 * 4^x) for some x. */ +static int is_good_size(u64 sz) +{ + int log = ilog2(phys_size); + if ((phys_size & (phys_size - 1)) || (log < 12) || (log & 1)) + return 0; + return 1; +} + +/* Hook from arch/powerpc/mm/mem.c */ +int usdpaa_test_fault(unsigned long pfn, u64 *phys_addr, u64 *size) +{ + struct mem_fragment *frag; + int idx = -1; + if ((pfn < pfn_start) || (pfn >= (pfn_start + pfn_size))) + return -1; + /* It's in-range, we need to find the fragment */ + spin_lock(&mem_lock); + list_for_each_entry(frag, &mem_list, list) { + if ((pfn >= frag->pfn_base) && (pfn < (frag->pfn_base + + frag->pfn_len))) { + *phys_addr = frag->base; + *size = frag->len; + idx = current_tlb++; + if (current_tlb >= (first_tlb + num_tlb)) + current_tlb = first_tlb; + break; + } + } + spin_unlock(&mem_lock); + return idx; +} + +static int usdpaa_open(struct inode *inode, struct file *filp) +{ + const struct alloc_backend *backend = &alloc_backends[0]; + struct ctx *ctx = kmalloc(sizeof(struct ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + filp->private_data = ctx; + + while (backend->id_type != usdpaa_id_max) { + dpa_alloc_init(&ctx->ids[backend->id_type]); + backend++; + } + + INIT_LIST_HEAD(&ctx->maps); + + filp->f_mapping->backing_dev_info = &directly_mappable_cdev_bdi; + + return 0; +} + +static int usdpaa_release(struct inode *inode, struct file *filp) +{ + struct ctx *ctx = filp->private_data; + struct mem_mapping *map, *tmp; + const struct alloc_backend *backend = &alloc_backends[0]; + while (backend->id_type != usdpaa_id_max) { + int ret, leaks = 0; + do { + u32 id, num; + ret = dpa_alloc_pop(&ctx->ids[backend->id_type], + &id, &num); + if (!ret) { + leaks += num; + backend->release(id, num); + } + } while (ret == 1); + if (leaks) + pr_crit("USDPAA process leaking %d %s%s\n", leaks, + backend->acronym, (leaks > 1) ? "s" : ""); + backend++; + } + spin_lock(&mem_lock); + list_for_each_entry_safe(map, tmp, &ctx->maps, list) { + if (map->frag->has_locking && (map->frag->owner == map)) { + map->frag->owner = NULL; + wake_up(&map->frag->wq); + } + if (!--map->frag->refs) { + struct mem_fragment *frag = map->frag; + do { + frag = merge_frag(frag); + } while (frag); + } + list_del(&map->list); + kfree(map); + } + spin_unlock(&mem_lock); + kfree(ctx); + return 0; +} + +static int usdpaa_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct ctx *ctx = filp->private_data; + struct mem_mapping *map; + int ret = 0; + + spin_lock(&mem_lock); + list_for_each_entry(map, &ctx->maps, list) { + if (map->frag->pfn_base == vma->vm_pgoff) + goto map_match; + } + spin_unlock(&mem_lock); + return -ENOMEM; + +map_match: + if (map->frag->len != (vma->vm_end - vma->vm_start)) + ret = -EINVAL; + spin_unlock(&mem_lock); + if (!ret) + ret = remap_pfn_range(vma, vma->vm_start, map->frag->pfn_base, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + return ret; +} + +/* Return the nearest rounded-up address >= 'addr' that is 'sz'-aligned. 'sz' + * must be a power of 2, but both 'addr' and 'sz' can be expressions. */ +#define USDPAA_MEM_ROUNDUP(addr, sz) \ + ({ \ + unsigned long foo_align = (sz) - 1; \ + ((addr) + foo_align) & ~foo_align; \ + }) +/* Searching for a size-aligned virtual address range starting from 'addr' */ +static unsigned long usdpaa_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct vm_area_struct *vma; + + if (!is_good_size(len)) + return -EINVAL; + + addr = USDPAA_MEM_ROUNDUP(addr, len); + vma = find_vma(current->mm, addr); + /* Keep searching until we reach the end of currently-used virtual + * address-space or we find a big enough gap. */ + while (vma) { + if ((addr + len) < vma->vm_start) + return addr; + addr = USDPAA_MEM_ROUNDUP(vma->vm_end, len); + vma = vma->vm_next; + } + if ((TASK_SIZE - len) < addr) + return -ENOMEM; + return addr; +} + +static long ioctl_id_alloc(struct ctx *ctx, void __user *arg) +{ + struct usdpaa_ioctl_id_alloc i; + const struct alloc_backend *backend; + int ret = copy_from_user(&i, arg, sizeof(i)); + if (ret) + return ret; + if ((i.id_type >= usdpaa_id_max) || !i.num) + return -EINVAL; + backend = &alloc_backends[i.id_type]; + /* Allocate the required resource type */ + ret = backend->alloc(&i.base, i.num, i.align, i.partial); + if (ret < 0) + return ret; + i.num = ret; + /* Copy the result to user-space */ + ret = copy_to_user(arg, &i, sizeof(i)); + if (ret) { + backend->release(i.base, i.num); + return ret; + } + /* Assign the allocated range to the FD accounting */ + dpa_alloc_free(&ctx->ids[i.id_type], i.base, i.num); + return 0; +} + +static long ioctl_id_release(struct ctx *ctx, void __user *arg) +{ + struct usdpaa_ioctl_id_release i; + const struct alloc_backend *backend; + int ret = copy_from_user(&i, arg, sizeof(i)); + if (ret) + return ret; + if ((i.id_type >= usdpaa_id_max) || !i.num) + return -EINVAL; + backend = &alloc_backends[i.id_type]; + /* Pull the range out of the FD accounting - the range is valid iff this + * succeeds. */ + ret = dpa_alloc_reserve(&ctx->ids[i.id_type], i.base, i.num); + if (ret) + return ret; + /* Release the resource to the backend */ + backend->release(i.base, i.num); + return 0; +} + +static long ioctl_dma_map(struct ctx *ctx, void __user *arg) +{ + struct usdpaa_ioctl_dma_map i; + struct mem_fragment *frag; + struct mem_mapping *map, *tmp; + u64 search_size; + int ret = copy_from_user(&i, arg, sizeof(i)); + if (ret) + return ret; + if (i.len && !is_good_size(i.len)) + return -EINVAL; + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + spin_lock(&mem_lock); + if (i.flags & USDPAA_DMA_FLAG_SHARE) { + list_for_each_entry(frag, &mem_list, list) { + if (frag->refs && (frag->flags & + USDPAA_DMA_FLAG_SHARE) && + !strncmp(i.name, frag->name, + USDPAA_DMA_NAME_MAX)) { + /* Matching entry */ + if ((i.flags & USDPAA_DMA_FLAG_CREATE) && + !(i.flags & USDPAA_DMA_FLAG_LAZY)) { + ret = -EBUSY; + goto out; + } + list_for_each_entry(tmp, &ctx->maps, list) + if (tmp->frag == frag) { + ret = -EBUSY; + goto out; + } + i.has_locking = frag->has_locking; + i.did_create = 0; + i.len = frag->len; + goto do_map; + } + } + /* No matching entry */ + if (!(i.flags & USDPAA_DMA_FLAG_CREATE)) { + ret = -ENOMEM; + goto out; + } + } + /* New fragment required, size must be provided. */ + if (!i.len) { + ret = -EINVAL; + goto out; + } + /* We search for the required size and if that fails, for the next + * biggest size, etc. */ + for (search_size = i.len; search_size <= phys_size; search_size <<= 2) { + list_for_each_entry(frag, &mem_list, list) { + if (!frag->refs && (frag->len == search_size)) { + while (frag->len > i.len) { + frag = split_frag(frag); + if (!frag) { + ret = -ENOMEM; + goto out; + } + } + frag->flags = i.flags; + strncpy(frag->name, i.name, + USDPAA_DMA_NAME_MAX); + frag->has_locking = i.has_locking; + init_waitqueue_head(&frag->wq); + frag->owner = NULL; + i.did_create = 1; + goto do_map; + } + } + } + ret = -ENOMEM; + goto out; + +do_map: + map->frag = frag; + frag->refs++; + list_add(&map->list, &ctx->maps); + i.pa_offset = frag->base; + +out: + spin_unlock(&mem_lock); + if (!ret) + ret = copy_to_user(arg, &i, sizeof(i)); + else + kfree(map); + return ret; +} + +static int test_lock(struct mem_mapping *map) +{ + int ret = 0; + spin_lock(&mem_lock); + if (!map->frag->owner) { + map->frag->owner = map; + ret = 1; + } + spin_unlock(&mem_lock); + return ret; +} + +static long ioctl_dma_lock(struct ctx *ctx, void __user *arg) +{ + struct mem_mapping *map; + struct vm_area_struct *vma; + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, (unsigned long)arg); + if (!vma || (vma->vm_start > (unsigned long)arg)) { + up_read(¤t->mm->mmap_sem); + return -EFAULT; + } + spin_lock(&mem_lock); + list_for_each_entry(map, &ctx->maps, list) { + if (map->frag->pfn_base == vma->vm_pgoff) + goto map_match; + } + map = NULL; +map_match: + spin_unlock(&mem_lock); + up_read(¤t->mm->mmap_sem); + + if (!map->frag->has_locking) + return -ENODEV; + return wait_event_interruptible(map->frag->wq, test_lock(map)); +} + +static long ioctl_dma_unlock(struct ctx *ctx, void __user *arg) +{ + struct mem_mapping *map; + struct vm_area_struct *vma; + int ret; + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, (unsigned long)arg); + if (!vma || (vma->vm_start > (unsigned long)arg)) + ret = -EFAULT; + else { + spin_lock(&mem_lock); + list_for_each_entry(map, &ctx->maps, list) { + if (map->frag->pfn_base == vma->vm_pgoff) { + if (!map->frag->has_locking) + ret = -ENODEV; + else if (map->frag->owner == map) { + map->frag->owner = NULL; + wake_up(&map->frag->wq); + ret = 0; + } else + ret = -EBUSY; + goto map_match; + } + } + ret = -EINVAL; +map_match: + spin_unlock(&mem_lock); + } + up_read(¤t->mm->mmap_sem); + return ret; +} + +static long usdpaa_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + struct ctx *ctx = fp->private_data; + void __user *a = (void __user *)arg; + switch (cmd) { + case USDPAA_IOCTL_ID_ALLOC: + return ioctl_id_alloc(ctx, a); + case USDPAA_IOCTL_ID_RELEASE: + return ioctl_id_release(ctx, a); + case USDPAA_IOCTL_DMA_MAP: + return ioctl_dma_map(ctx, a); + case USDPAA_IOCTL_DMA_LOCK: + return ioctl_dma_lock(ctx, a); + case USDPAA_IOCTL_DMA_UNLOCK: + return ioctl_dma_unlock(ctx, a); + } + return -EINVAL; +} + +static const struct file_operations usdpaa_fops = { + .open = usdpaa_open, + .release = usdpaa_release, + .mmap = usdpaa_mmap, + .get_unmapped_area = usdpaa_get_unmapped_area, + .unlocked_ioctl = usdpaa_ioctl, + .compat_ioctl = usdpaa_ioctl +}; + +static struct miscdevice usdpaa_miscdev = { + .name = "fsl-usdpaa", + .fops = &usdpaa_fops, + .minor = MISC_DYNAMIC_MINOR, +}; + +/* Early-boot memory allocation. The boot-arg "usdpaa_mem=<x>" is used to + * indicate how much memory (if any) to allocate during early boot. If the + * format "usdpaa_mem=<x>,<y>" is used, then <y> will be interpreted as the + * number of TLB1 entries to reserve (default is 1). If there are more mappings + * than there are TLB1 entries, fault-handling will occur. */ +static __init int usdpaa_mem(char *arg) +{ + phys_size = memparse(arg, &arg); + num_tlb = 1; + if (*arg == ',') { + unsigned long ul; + int err = kstrtoul(arg + 1, 0, &ul); + if (err < 0) { + num_tlb = 1; + pr_warning("ERROR, usdpaa_mem arg is invalid\n"); + } else + num_tlb = (unsigned int)ul; + } + return 0; +} +early_param("usdpaa_mem", usdpaa_mem); + +__init void fsl_usdpaa_init_early(void) +{ + if (!phys_size) { + pr_info("No USDPAA memory, no 'usdpaa_mem' bootarg\n"); + return; + } + if (!is_good_size(phys_size)) { + pr_err("'usdpaa_mem' bootarg must be 4096*4^x\n"); + phys_size = 0; + return; + } + phys_start = memblock_alloc(phys_size, phys_size); + if (!phys_start) { + pr_err("Failed to reserve USDPAA region (sz:%llx)\n", + phys_size); + return; + } + pfn_start = phys_start >> PAGE_SHIFT; + pfn_size = phys_size >> PAGE_SHIFT; + first_tlb = current_tlb = tlbcam_index; + tlbcam_index += num_tlb; + pr_info("USDPAA region at %llx:%llx(%lx:%lx), %d TLB1 entries)\n", + phys_start, phys_size, pfn_start, pfn_size, num_tlb); +} + +static int __init usdpaa_init(void) +{ + struct mem_fragment *frag; + int ret; + + pr_info("Freescale USDPAA process driver\n"); + if (!phys_start) { + pr_warning("fsl-usdpaa: no region found\n"); + return 0; + } + frag = kmalloc(sizeof(*frag), GFP_KERNEL); + if (!frag) { + pr_err("Failed to setup USDPAA memory accounting\n"); + return -ENOMEM; + } + frag->base = phys_start; + frag->len = phys_size; + frag->pfn_base = pfn_start; + frag->pfn_len = pfn_size; + frag->refs = 0; + init_waitqueue_head(&frag->wq); + frag->owner = NULL; + list_add(&frag->list, &mem_list); + ret = misc_register(&usdpaa_miscdev); + if (ret) + pr_err("fsl-usdpaa: failed to register misc device\n"); + return ret; +} + +static void __exit usdpaa_exit(void) +{ + misc_deregister(&usdpaa_miscdev); +} + +module_init(usdpaa_init); +module_exit(usdpaa_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Freescale USDPAA process driver"); |