From patchwork Fri Aug 20 09:50:45 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?b?TWljaGHDheKAmiBOYXphcmV3aWN6?= X-Patchwork-Id: 120609 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.4/8.14.3) with ESMTP id o7K9qtU6025222 for ; Fri, 20 Aug 2010 09:52:56 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751688Ab0HTJwT (ORCPT ); Fri, 20 Aug 2010 05:52:19 -0400 Received: from mailout3.w1.samsung.com ([210.118.77.13]:41401 "EHLO mailout3.w1.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751561Ab0HTJwQ (ORCPT ); Fri, 20 Aug 2010 05:52:16 -0400 MIME-version: 1.0 Content-transfer-encoding: 7BIT Content-type: TEXT/PLAIN Received: from eu_spt1 ([210.118.77.13]) by mailout3.w1.samsung.com (Sun Java(tm) System Messaging Server 6.3-8.04 (built Jul 29 2009; 32bit)) with ESMTP id <0L7G0031F3F2QJ70@mailout3.w1.samsung.com>; Fri, 20 Aug 2010 10:52:14 +0100 (BST) Received: from pikus.localdomain ([10.89.8.241]) by spt1.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0L7G008IB3CR4S@spt1.w1.samsung.com>; Fri, 20 Aug 2010 10:52:14 +0100 (BST) Date: Fri, 20 Aug 2010 11:50:45 +0200 From: Michal Nazarewicz Subject: [PATCH/RFCv4 5/6] mm: cma: Test device and application added In-reply-to: <8fa83f632d8198f98b232b96c848eece44e33f83.1282286941.git.m.nazarewicz@samsung.com> To: linux-mm@kvack.org Cc: Daniel Walker , FUJITA Tomonori , Hans Verkuil , Jonathan Corbet , Konrad Rzeszutek Wilk , Kyungmin Park , Marek Szyprowski , Mark Brown , Pawel Osciak , Russell King , Zach Pfeffer , linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Message-id: <2e2a3d55b07cf8ce852e0d02e6fd77dc1fcbf275.1282286941.git.m.nazarewicz@samsung.com> X-Mailer: git-send-email 1.7.1 References: <0b02e05fc21e70a3af39e65e628d117cd89d70a1.1282286941.git.m.nazarewicz@samsung.com> <343f4b0edf9b5eef598831700cb459cd428d3f2e.1282286941.git.m.nazarewicz@samsung.com> <9883433f103cc84e55db150806d2270200c74c6b.1282286941.git.m.nazarewicz@samsung.com> <8fa83f632d8198f98b232b96c848eece44e33f83.1282286941.git.m.nazarewicz@samsung.com> Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Fri, 20 Aug 2010 09:52:56 +0000 (UTC) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 0b591b6..f93e812 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -395,4 +395,12 @@ source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" source "drivers/misc/iwmc3200top/Kconfig" +config CMA_DEVICE + tristate "CMA misc device (DEVELOPEMENT)" + depends on CMA_DEVELOPEMENT + help + The CMA misc device allows allocating contiguous memory areas + from user space. This is mostly for testing of the CMA + framework. + endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 255a80d..2e82898 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -35,3 +35,4 @@ obj-y += eeprom/ obj-y += cb710/ obj-$(CONFIG_VMWARE_BALLOON) += vmware_balloon.o obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o +obj-$(CONFIG_CMA_DEVICE) += cma-dev.o diff --git a/drivers/misc/cma-dev.c b/drivers/misc/cma-dev.c new file mode 100644 index 0000000..de534f0 --- /dev/null +++ b/drivers/misc/cma-dev.c @@ -0,0 +1,185 @@ +/* + * Contiguous Memory Allocator userspace driver + * Copyright (c) 2010 by Samsung Electronics. + * Written by Michal Nazarewicz (m.nazarewicz@samsung.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License or (at your optional) any later version of the license. + */ + +#define pr_fmt(fmt) "cma: " fmt + +#ifdef CONFIG_CMA_DEBUG +# define DEBUG +#endif + +#include /* Error numbers */ +#include /* IS_ERR_VALUE() */ +#include /* struct file */ +#include /* Memory stuff */ +#include +#include +#include /* Standard module stuff */ +#include /* struct device, dev_dbg() */ +#include /* Just to be safe ;) */ +#include /* __copy_{to,from}_user */ +#include /* misc_register() and company */ + +#include + +static int cma_file_open(struct inode *inode, struct file *file); +static int cma_file_release(struct inode *inode, struct file *file); +static long cma_file_ioctl(struct file *file, unsigned cmd, unsigned long arg); +static int cma_file_mmap(struct file *file, struct vm_area_struct *vma); + + +static struct miscdevice cma_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "cma", + .fops = &(const struct file_operations) { + .owner = THIS_MODULE, + .open = cma_file_open, + .release = cma_file_release, + .unlocked_ioctl = cma_file_ioctl, + .mmap = cma_file_mmap, + }, +}; +#define cma_dev (cma_miscdev.this_device) + + +#define cma_file_start(file) (((dma_addr_t *)(file)->private_data)[0]) +#define cma_file_size(file) (((dma_addr_t *)(file)->private_data)[1]) + + +static int cma_file_open(struct inode *inode, struct file *file) +{ + dev_dbg(cma_dev, "%s(%p)\n", __func__, (void *)file); + + file->private_data = NULL; + + return 0; +} + + +static int cma_file_release(struct inode *inode, struct file *file) +{ + dev_dbg(cma_dev, "%s(%p)\n", __func__, (void *)file); + + if (file->private_data) { + cma_free(cma_file_start(file)); + kfree(file->private_data); + } + + return 0; +} + + +static long cma_file_ioctl(struct file *file, unsigned cmd, unsigned long arg) +{ + struct cma_alloc_request req; + struct device fake_device; + unsigned long addr; + long ret; + + dev_dbg(cma_dev, "%s(%p)\n", __func__, (void *)file); + + if (cmd != IOCTL_CMA_ALLOC) + return -ENOTTY; + + if (!arg) + return -EINVAL; + + if (file->private_data) /* Already allocated */ + return -EBADFD; + + if (copy_from_user(&req, (void *)arg, sizeof req)) + return -EFAULT; + + if (req.magic != CMA_MAGIC) + return -ENOTTY; + + /* May happen on 32 bit system. */ + if (req.size > ~(typeof(req.size))0 || + req.alignment > ~(typeof(req.alignment))0) + return -EINVAL; + + if (strnlen(req.name, sizeof req.name) >= sizeof req.name + || strnlen(req.kind, sizeof req.kind) >= sizeof req.kind) + return -EINVAL; + + file->private_data = kmalloc(2 * sizeof(dma_addr_t), GFP_KERNEL); + if (!file->private_data) + return -ENOMEM; + + fake_device.init_name = req.name; + fake_device.kobj.name = req.name; + addr = cma_alloc(&fake_device, req.kind, req.size, req.alignment); + if (IS_ERR_VALUE(addr)) { + ret = addr; + goto error_priv; + } + + if (put_user(addr, (typeof(req.start) *)(arg + offsetof(typeof(req), + start)))) { + ret = -EFAULT; + goto error_put; + } + + cma_file_start(file) = addr; + cma_file_size(file) = req.size; + + dev_dbg(cma_dev, "allocated %p@%p\n", + (void *)(dma_addr_t)req.size, (void *)addr); + + return 0; + +error_put: + cma_free(addr); +error_priv: + kfree(file->private_data); + file->private_data = NULL; + return ret; +} + + +static int cma_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long pgoff, offset, length; + + dev_dbg(cma_dev, "%s(%p)\n", __func__, (void *)file); + + if (!file->private_data) + return -EBADFD; + + pgoff = vma->vm_pgoff; + offset = pgoff << PAGE_SHIFT; + length = vma->vm_end - vma->vm_start; + + if (offset >= cma_file_size(file) + || length > cma_file_size(file) + || offset + length > cma_file_size(file)) + return -ENOSPC; + + return remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(cma_file_start(file) + offset), + length, vma->vm_page_prot); +} + + + +static int __init cma_dev_init(void) +{ + int ret = misc_register(&cma_miscdev); + pr_debug("miscdev: register returned: %d\n", ret); + return ret; +} +module_init(cma_dev_init); + +static void __exit cma_dev_exit(void) +{ + dev_dbg(cma_dev, "deregisterring\n"); + misc_deregister(&cma_miscdev); +} +module_exit(cma_dev_exit); diff --git a/include/linux/cma.h b/include/linux/cma.h index eede28d..4334bb8 100644 --- a/include/linux/cma.h +++ b/include/linux/cma.h @@ -11,6 +11,36 @@ * See Documentation/contiguous-memory.txt for details. */ +#include +#include + + +#define CMA_MAGIC (('c' << 24) | ('M' << 16) | ('a' << 8) | 0x42) + +/** + * An information about area exportable to user space. + * @magic: must always be CMA_MAGIC. + * @name: name of the device to allocate as. + * @kind: kind of the memory. + * @_pad: reserved. + * @size: size of the chunk to allocate. + * @alignment: desired alignment of the chunk (must be power of two or zero). + * @start: when ioctl() finishes this stores physical address of the chunk. + */ +struct cma_alloc_request { + __u32 magic; + char name[17]; + char kind[17]; + __u16 pad; + /* __u64 to be compatible accross 32 and 64 bit systems. */ + __u64 size; + __u64 alignment; + __u64 start; +}; + +#define IOCTL_CMA_ALLOC _IOWR('p', 0, struct cma_alloc_request) + + /***************************** Kernel lever API *****************************/ #ifdef __KERNEL__ diff --git a/tools/cma/cma-test.c b/tools/cma/cma-test.c new file mode 100644 index 0000000..567c57b --- /dev/null +++ b/tools/cma/cma-test.c @@ -0,0 +1,373 @@ +/* + * cma-test.c -- CMA testing application + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* $(CROSS_COMPILE)gcc -Wall -Wextra -g -o cma-test cma-test.c */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + + +static void handle_command(char *line); + +int main(void) +{ + unsigned no = 1; + char line[1024]; + int skip = 0; + + fputs("commands:\n" + " l or list list allocated chunks\n" + " a or alloc [/] allocate chunk\n" + " f or free [] free an chunk\n" + " # ... comment\n" + " repeat previous\n" + "\n", stderr); + + while (fgets(line, sizeof line, stdin)) { + char *nl = strchr(line, '\n'); + if (nl) { + if (skip) { + fprintf(stderr, "cma: %d: line too long\n", no); + skip = 0; + } else { + *nl = '\0'; + handle_command(line); + } + ++no; + } else { + skip = 1; + } + } + + if (skip) + fprintf(stderr, "cma: %d: no new line at EOF\n", no); + return 0; +} + + + +static void cmd_list(char *name, char *line); +static void cmd_alloc(char *name, char *line); +static void cmd_free(char *name, char *line); + +static const struct command { + const char name[8]; + void (*handle)(char *name, char *line); +} commands[] = { + { "list", cmd_list }, + { "l", cmd_list }, + { "alloc", cmd_alloc }, + { "a", cmd_alloc }, + { "free", cmd_free }, + { "f", cmd_free }, + { "", NULL } +}; + + +#define SKIP_SPACE(ch) do while (isspace(*(ch))) ++(ch); while (0) + + +static void handle_command(char *line) +{ + static char last_line[1024]; + + const struct command *cmd; + char *name; + + SKIP_SPACE(line); + if (*line == '#') + return; + + if (!*line) + strcpy(line, last_line); + else + strcpy(last_line, line); + + name = line; + while (*line && !isspace(*line)) + ++line; + + if (*line) { + *line = '\0'; + ++line; + } + + for (cmd = commands; *(cmd->name); ++cmd) + if (!strcmp(name, cmd->name)) { + cmd->handle(name, line); + return; + } + + fprintf(stderr, "%s: unknown command\n", name); +} + + + +struct chunk { + struct chunk *next, *prev; + int fd; + unsigned long size; + unsigned long start; +}; + +static struct chunk root = { + .next = &root, + .prev = &root, +}; + +#define for_each(a) for (a = root.next; a != &root; a = a->next) + +static struct chunk *chunk_create(const char *prefix); +static void chunk_destroy(struct chunk *chunk); +static void chunk_add(struct chunk *chunk); + +static int memparse(char *ptr, char **retptr, unsigned long *ret); + + +static void cmd_list(char *name, char *line) +{ + struct chunk *chunk; + + (void)name; (void)line; + + for_each(chunk) + printf("%3d: %p@%p\n", chunk->fd, + (void *)chunk->size, (void *)chunk->start); +} + + +static void cmd_alloc(char *name, char *line) +{ + unsigned long size, alignment = 0; + struct cma_alloc_request req; + char *dev, *kind = NULL; + struct chunk *chunk; + int ret; + + SKIP_SPACE(line); + if (!*line) { + fprintf(stderr, "%s: expecting name\n", name); + return; + } + + for (dev = line; *line && !isspace(*line); ++line) + if (*line == '/') + kind = line; + + if (!*line) { + fprintf(stderr, "%s: expecting size after name\n", name); + return; + } + + if (kind) + *kind++ = '\0'; + *line++ = '\0'; + + if (( kind && (size_t)(kind - dev ) > sizeof req.name) + || (!kind && (size_t)(line - dev ) > sizeof req.name) + || ( kind && (size_t)(line - kind) > sizeof req.kind)) { + fprintf(stderr, "%s: name or kind too long\n", name); + return; + } + + + if (memparse(line, &line, &size) < 0 || !size) { + fprintf(stderr, "%s: invalid size\n", name); + return; + } + + if (*line == '/') + if (memparse(line, &line, &alignment) < 0) { + fprintf(stderr, "%s: invalid alignment\n", name); + return; + } + + SKIP_SPACE(line); + if (*line) { + fprintf(stderr, "%s: unknown arguments at the end: %s\n", + name, line); + return; + } + + + chunk = chunk_create(name); + if (!chunk) + return; + + fprintf(stderr, "%s: allocating %p/%p\n", name, + (void *)size, (void *)alignment); + + req.magic = CMA_MAGIC; + req.size = size; + req.alignment = alignment; + + strcpy(req.name, dev); + if (kind) + strcpy(req.kind, kind); + else + req.kind[0] = '\0'; + + + ret = ioctl(chunk->fd, IOCTL_CMA_ALLOC, &req); + if (ret < 0) { + fprintf(stderr, "%s: cma_alloc: %s\n", name, strerror(errno)); + chunk_destroy(chunk); + } else { + chunk_add(chunk); + chunk->size = req.size; + chunk->start = req.start; + + printf("%3d: %p@%p\n", chunk->fd, + (void *)chunk->size, (void *)chunk->start); + } +} + + +static void cmd_free(char *name, char *line) +{ + struct chunk *chunk; + + SKIP_SPACE(line); + + if (*line) { + unsigned long num; + + errno = 0; + num = strtoul(line, &line, 10); + + if (errno || num > INT_MAX) { + fprintf(stderr, "%s: invalid number\n", name); + return; + } + + SKIP_SPACE(line); + if (*line) { + fprintf(stderr, "%s: unknown arguments at the end: %s\n", + name, line); + return; + } + + for_each(chunk) + if (chunk->fd == (int)num) + goto ok; + fprintf(stderr, "%s: no chunk %3lu\n", name, num); + return; + + } else { + chunk = root.prev; + if (chunk == &root) { + fprintf(stderr, "%s: no chunks\n", name); + return; + } + } + +ok: + fprintf(stderr, "%s: freeing %p@%p\n", name, + (void *)chunk->size, (void *)chunk->start); + chunk_destroy(chunk); +} + + +static struct chunk *chunk_create(const char *prefix) +{ + struct chunk *chunk; + int fd; + + chunk = malloc(sizeof *chunk); + if (!chunk) { + fprintf(stderr, "%s: %s\n", prefix, strerror(errno)); + return NULL; + } + + fd = open("/dev/cma", O_RDWR); + if (fd < 0) { + fprintf(stderr, "%s: /dev/cma: %s\n", prefix, strerror(errno)); + return NULL; + } + + chunk->prev = chunk; + chunk->next = chunk; + chunk->fd = fd; + return chunk; +} + +static void chunk_destroy(struct chunk *chunk) +{ + chunk->prev->next = chunk->next; + chunk->next->prev = chunk->prev; + close(chunk->fd); +} + +static void chunk_add(struct chunk *chunk) +{ + chunk->next = &root; + chunk->prev = root.prev; + root.prev->next = chunk; + root.prev = chunk; +} + + + +static int memparse(char *ptr, char **retptr, unsigned long *ret) +{ + unsigned long val; + + SKIP_SPACE(ptr); + + errno = 0; + val = strtoul(ptr, &ptr, 0); + if (errno) + return -1; + + switch (*ptr) { + case 'G': + case 'g': + val <<= 10; + case 'M': + case 'm': + val <<= 10; + case 'K': + case 'k': + val <<= 10; + ++ptr; + } + + if (retptr) { + SKIP_SPACE(ptr); + *retptr = ptr; + } + + *ret = val; + return 0; +}