diff options
Diffstat (limited to 'drivers/char/broadcom')
| -rw-r--r-- | drivers/char/broadcom/Kconfig | 33 | ||||
| -rw-r--r-- | drivers/char/broadcom/Makefile | 3 | ||||
| -rw-r--r-- | drivers/char/broadcom/bcm2835_smi_dev.c | 408 | ||||
| -rw-r--r-- | drivers/char/broadcom/vc_mem.c | 635 | ||||
| -rw-r--r-- | drivers/char/broadcom/vcio.c | 187 |
5 files changed, 1266 insertions, 0 deletions
diff --git a/drivers/char/broadcom/Kconfig b/drivers/char/broadcom/Kconfig new file mode 100644 index 000000000000..29d880d47282 --- /dev/null +++ b/drivers/char/broadcom/Kconfig @@ -0,0 +1,33 @@ +# +# Broadcom char driver config +# + +menuconfig BRCM_CHAR_DRIVERS + bool "Broadcom Char Drivers" + help + Broadcom's char drivers + +if BRCM_CHAR_DRIVERS + +config BCM2708_VCMEM + bool "Videocore Memory" + default y + help + Helper for videocore memory access and total size allocation. + +config BCM_VCIO + tristate "Mailbox userspace access" + depends on BCM2835_MBOX + help + Gives access to the mailbox property channel from userspace. + +endif + +config BCM2835_SMI_DEV + tristate "Character device driver for BCM2835 Secondary Memory Interface" + depends on BCM2835_SMI + default m + help + This driver provides a character device interface (ioctl + read/write) to + Broadcom's Secondary Memory interface. The low-level functionality is provided + by the SMI driver itself. diff --git a/drivers/char/broadcom/Makefile b/drivers/char/broadcom/Makefile new file mode 100644 index 000000000000..2ae3e9d411e9 --- /dev/null +++ b/drivers/char/broadcom/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_BCM2708_VCMEM) += vc_mem.o +obj-$(CONFIG_BCM_VCIO) += vcio.o +obj-$(CONFIG_BCM2835_SMI_DEV) += bcm2835_smi_dev.o diff --git a/drivers/char/broadcom/bcm2835_smi_dev.c b/drivers/char/broadcom/bcm2835_smi_dev.c new file mode 100644 index 000000000000..ae0c0d24fad9 --- /dev/null +++ b/drivers/char/broadcom/bcm2835_smi_dev.c @@ -0,0 +1,408 @@ +/** + * Character device driver for Broadcom Secondary Memory Interface + * + * Written by Luke Wren <[email protected]> + * Copyright (c) 2015, Raspberry Pi (Trading) Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/fs.h> + +#include <linux/broadcom/bcm2835_smi.h> + +#define DEVICE_NAME "bcm2835-smi-dev" +#define DRIVER_NAME "smi-dev-bcm2835" +#define DEVICE_MINOR 0 + +static struct cdev bcm2835_smi_cdev; +static dev_t bcm2835_smi_devid; +static struct class *bcm2835_smi_class; +static struct device *bcm2835_smi_dev; + +struct bcm2835_smi_dev_instance { + struct device *dev; +}; + +static struct bcm2835_smi_instance *smi_inst; +static struct bcm2835_smi_dev_instance *inst; + +static const char *const ioctl_names[] = { + "READ_SETTINGS", + "WRITE_SETTINGS", + "ADDRESS" +}; + +/**************************************************************************** +* +* SMI chardev file ops +* +***************************************************************************/ +static long +bcm2835_smi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + long ret = 0; + + dev_info(inst->dev, "serving ioctl..."); + + switch (cmd) { + case BCM2835_SMI_IOC_GET_SETTINGS:{ + struct smi_settings *settings; + + dev_info(inst->dev, "Reading SMI settings to user."); + settings = bcm2835_smi_get_settings_from_regs(smi_inst); + if (copy_to_user((void *)arg, settings, + sizeof(struct smi_settings))) + dev_err(inst->dev, "settings copy failed."); + break; + } + case BCM2835_SMI_IOC_WRITE_SETTINGS:{ + struct smi_settings *settings; + + dev_info(inst->dev, "Setting user's SMI settings."); + settings = bcm2835_smi_get_settings_from_regs(smi_inst); + if (copy_from_user(settings, (void *)arg, + sizeof(struct smi_settings))) + dev_err(inst->dev, "settings copy failed."); + else + bcm2835_smi_set_regs_from_settings(smi_inst); + break; + } + case BCM2835_SMI_IOC_ADDRESS: + dev_info(inst->dev, "SMI address set: 0x%02x", (int)arg); + bcm2835_smi_set_address(smi_inst, arg); + break; + default: + dev_err(inst->dev, "invalid ioctl cmd: %d", cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +static int bcm2835_smi_open(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + + dev_dbg(inst->dev, "SMI device opened."); + + if (dev != DEVICE_MINOR) { + dev_err(inst->dev, + "bcm2835_smi_release: Unknown minor device: %d", + dev); + return -ENXIO; + } + + return 0; +} + +static int bcm2835_smi_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + + if (dev != DEVICE_MINOR) { + dev_err(inst->dev, + "bcm2835_smi_release: Unknown minor device %d", dev); + return -ENXIO; + } + + return 0; +} + +static ssize_t dma_bounce_user( + enum dma_transfer_direction dma_dir, + char __user *user_ptr, + size_t count, + struct bcm2835_smi_bounce_info *bounce) +{ + int chunk_size; + int chunk_no = 0; + int count_left = count; + + while (count_left) { + int rv; + void *buf; + + /* Wait for current chunk to complete: */ + if (down_timeout(&bounce->callback_sem, + msecs_to_jiffies(1000))) { + dev_err(inst->dev, "DMA bounce timed out"); + count -= (count_left); + break; + } + + if (bounce->callback_sem.count >= DMA_BOUNCE_BUFFER_COUNT - 1) + dev_err(inst->dev, "WARNING: Ring buffer overflow"); + chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ? + DMA_BOUNCE_BUFFER_SIZE : count_left; + buf = bounce->buffer[chunk_no % DMA_BOUNCE_BUFFER_COUNT]; + if (dma_dir == DMA_DEV_TO_MEM) + rv = copy_to_user(user_ptr, buf, chunk_size); + else + rv = copy_from_user(buf, user_ptr, chunk_size); + if (rv) + dev_err(inst->dev, "copy_*_user() failed!: %d", rv); + user_ptr += chunk_size; + count_left -= chunk_size; + chunk_no++; + } + return count; +} + +static ssize_t +bcm2835_read_file(struct file *f, char __user *user_ptr, + size_t count, loff_t *offs) +{ + int odd_bytes; + size_t count_check; + + dev_dbg(inst->dev, "User reading %zu bytes from SMI.", count); + /* We don't want to DMA a number of bytes % 4 != 0 (32 bit FIFO) */ + if (count > DMA_THRESHOLD_BYTES) + odd_bytes = count & 0x3; + else + odd_bytes = count; + count -= odd_bytes; + count_check = count; + if (count) { + struct bcm2835_smi_bounce_info *bounce; + + count = bcm2835_smi_user_dma(smi_inst, + DMA_DEV_TO_MEM, user_ptr, count, + &bounce); + if (count) + count = dma_bounce_user(DMA_DEV_TO_MEM, user_ptr, + count, bounce); + } + if (odd_bytes && (count == count_check)) { + /* Read from FIFO directly if not using DMA */ + uint8_t buf[DMA_THRESHOLD_BYTES]; + unsigned long bytes_not_transferred; + + bcm2835_smi_read_buf(smi_inst, buf, odd_bytes); + bytes_not_transferred = copy_to_user(user_ptr + count, buf, odd_bytes); + if (bytes_not_transferred) + dev_err(inst->dev, "copy_to_user() failed."); + count += odd_bytes - bytes_not_transferred; + } + return count; +} + +static ssize_t +bcm2835_write_file(struct file *f, const char __user *user_ptr, + size_t count, loff_t *offs) +{ + int odd_bytes; + size_t count_check; + + dev_dbg(inst->dev, "User writing %zu bytes to SMI.", count); + if (count > DMA_THRESHOLD_BYTES) + odd_bytes = count & 0x3; + else + odd_bytes = count; + count -= odd_bytes; + count_check = count; + if (count) { + struct bcm2835_smi_bounce_info *bounce; + + count = bcm2835_smi_user_dma(smi_inst, + DMA_MEM_TO_DEV, (char __user *)user_ptr, count, + &bounce); + if (count) + count = dma_bounce_user(DMA_MEM_TO_DEV, + (char __user *)user_ptr, + count, bounce); + } + if (odd_bytes && (count == count_check)) { + uint8_t buf[DMA_THRESHOLD_BYTES]; + unsigned long bytes_not_transferred; + + bytes_not_transferred = copy_from_user(buf, user_ptr + count, odd_bytes); + if (bytes_not_transferred) + dev_err(inst->dev, "copy_from_user() failed."); + else + bcm2835_smi_write_buf(smi_inst, buf, odd_bytes); + count += odd_bytes - bytes_not_transferred; + } + return count; +} + +static const struct file_operations +bcm2835_smi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = bcm2835_smi_ioctl, + .open = bcm2835_smi_open, + .release = bcm2835_smi_release, + .read = bcm2835_read_file, + .write = bcm2835_write_file, +}; + + +/**************************************************************************** +* +* bcm2835_smi_probe - called when the driver is loaded. +* +***************************************************************************/ + +static int bcm2835_smi_dev_probe(struct platform_device *pdev) +{ + int err; + void *ptr_err; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node, *smi_node; + + if (!node) { + dev_err(dev, "No device tree node supplied!"); + return -EINVAL; + } + + smi_node = of_parse_phandle(node, "smi_handle", 0); + + if (!smi_node) { + dev_err(dev, "No such property: smi_handle"); + return -ENXIO; + } + + smi_inst = bcm2835_smi_get(smi_node); + + if (!smi_inst) + return -EPROBE_DEFER; + + /* Allocate buffers and instance data */ + + inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL); + + if (!inst) + return -ENOMEM; + + inst->dev = dev; + + /* Create character device entries */ + + err = alloc_chrdev_region(&bcm2835_smi_devid, + DEVICE_MINOR, 1, DEVICE_NAME); + if (err != 0) { + dev_err(inst->dev, "unable to allocate device number"); + return -ENOMEM; + } + cdev_init(&bcm2835_smi_cdev, &bcm2835_smi_fops); + bcm2835_smi_cdev.owner = THIS_MODULE; + err = cdev_add(&bcm2835_smi_cdev, bcm2835_smi_devid, 1); + if (err != 0) { + dev_err(inst->dev, "unable to register device"); + err = -ENOMEM; + goto failed_cdev_add; + } + + /* Create sysfs entries */ + + bcm2835_smi_class = class_create(DEVICE_NAME); + ptr_err = bcm2835_smi_class; + if (IS_ERR(ptr_err)) + goto failed_class_create; + + bcm2835_smi_dev = device_create(bcm2835_smi_class, NULL, + bcm2835_smi_devid, NULL, + "smi"); + ptr_err = bcm2835_smi_dev; + if (IS_ERR(ptr_err)) + goto failed_device_create; + + dev_info(inst->dev, "initialised"); + + return 0; + +failed_device_create: + class_destroy(bcm2835_smi_class); +failed_class_create: + cdev_del(&bcm2835_smi_cdev); + err = PTR_ERR(ptr_err); +failed_cdev_add: + unregister_chrdev_region(bcm2835_smi_devid, 1); + dev_err(dev, "could not load bcm2835_smi_dev"); + return err; +} + +/**************************************************************************** +* +* bcm2835_smi_remove - called when the driver is unloaded. +* +***************************************************************************/ + +static void bcm2835_smi_dev_remove(struct platform_device *pdev) +{ + device_destroy(bcm2835_smi_class, bcm2835_smi_devid); + class_destroy(bcm2835_smi_class); + cdev_del(&bcm2835_smi_cdev); + unregister_chrdev_region(bcm2835_smi_devid, 1); + + dev_info(inst->dev, "SMI character dev removed - OK"); +} + +/**************************************************************************** +* +* Register the driver with device tree +* +***************************************************************************/ + +static const struct of_device_id bcm2835_smi_dev_of_match[] = { + {.compatible = "brcm,bcm2835-smi-dev",}, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, bcm2835_smi_dev_of_match); + +static struct platform_driver bcm2835_smi_dev_driver = { + .probe = bcm2835_smi_dev_probe, + .remove = bcm2835_smi_dev_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcm2835_smi_dev_of_match, + }, +}; + +module_platform_driver(bcm2835_smi_dev_driver); + +MODULE_ALIAS("platform:smi-dev-bcm2835"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION( + "Character device driver for BCM2835's secondary memory interface"); +MODULE_AUTHOR("Luke Wren <[email protected]>"); diff --git a/drivers/char/broadcom/vc_mem.c b/drivers/char/broadcom/vc_mem.c new file mode 100644 index 000000000000..4b7faf084e0c --- /dev/null +++ b/drivers/char/broadcom/vc_mem.c @@ -0,0 +1,635 @@ +/* + * Copyright 2010 - 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/dma-mapping.h> +#include <linux/broadcom/vc_mem.h> +#include <linux/compat.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/platform_data/dma-bcm2708.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define DRIVER_NAME "vc-mem" + +/* N.B. These use a different magic value for compatibility with bmc7208_fb */ +#define VC_MEM_IOC_DMACOPY _IOW('z', 0x22, struct vc_mem_dmacopy) +#define VC_MEM_IOC_DMACOPY32 _IOW('z', 0x22, struct vc_mem_dmacopy32) + +/* address with no aliases */ +#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000) +/* cache coherent but non-allocating in L1 and L2 */ +#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000) + +/* Device (/dev) related variables */ +static dev_t vc_mem_devnum; +static struct class *vc_mem_class; +static struct cdev vc_mem_cdev; +static int vc_mem_inited; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *vc_mem_debugfs_entry; +#endif + +struct vc_mem_dmacopy { + void *dst; + __u32 src; + __u32 length; +}; + +#ifdef CONFIG_COMPAT +struct vc_mem_dmacopy32 { + compat_uptr_t dst; + __u32 src; + __u32 length; +}; +#endif + +/* + * Videocore memory addresses and size + * + * Drivers that wish to know the videocore memory addresses and sizes should + * use these variables instead of the MM_IO_BASE and MM_ADDR_IO defines in + * headers. This allows the other drivers to not be tied down to a a certain + * address/size at compile time. + * + * In the future, the goal is to have the videocore memory virtual address and + * size be calculated at boot time rather than at compile time. The decision of + * where the videocore memory resides and its size would be in the hands of the + * bootloader (and/or kernel). When that happens, the values of these variables + * would be calculated and assigned in the init function. + */ +/* In the 2835 VC in mapped above ARM, but ARM has full access to VC space */ +unsigned long mm_vc_mem_phys_addr; +EXPORT_SYMBOL(mm_vc_mem_phys_addr); +unsigned int mm_vc_mem_size; +EXPORT_SYMBOL(mm_vc_mem_size); +unsigned int mm_vc_mem_base; +EXPORT_SYMBOL(mm_vc_mem_base); + +static uint phys_addr; +static uint mem_size; +static uint mem_base; + +struct vc_mem_dma { + struct device *dev; + int dma_chan; + int dma_irq; + void __iomem *dma_chan_base; + wait_queue_head_t dma_waitq; + void *cb_base; /* DMA control blocks */ + dma_addr_t cb_handle; +}; + +struct { u32 base, length; } gpu_mem; +static struct mutex dma_mutex; +static struct vc_mem_dma vc_mem_dma; + +static int +vc_mem_open(struct inode *inode, struct file *file) +{ + (void)inode; + + pr_debug("%s: called file = 0x%p\n", __func__, file); + + return 0; +} + +static int +vc_mem_release(struct inode *inode, struct file *file) +{ + (void)inode; + + pr_debug("%s: called file = 0x%p\n", __func__, file); + + return 0; +} + +static void +vc_mem_get_size(void) +{ +} + +static void +vc_mem_get_base(void) +{ +} + +int +vc_mem_get_current_size(void) +{ + return mm_vc_mem_size; +} +EXPORT_SYMBOL_GPL(vc_mem_get_current_size); + +static int +vc_mem_dma_init(void) +{ + struct vc_mem_dma *vcdma = &vc_mem_dma; + struct platform_device *pdev; + struct device_node *fwnode; + struct rpi_firmware *fw; + struct device *dev; + u32 revision; + int rc; + + if (vcdma->dev) + return 0; + + fwnode = of_find_node_by_path("/system"); + rc = of_property_read_u32(fwnode, "linux,revision", &revision); + revision = (revision >> 12) & 0xf; + if (revision != 1 && revision != 2) { + /* Only BCM2709 and BCM2710 may have logs where the ARMs + * can't see them. + */ + return -ENXIO; + } + + fwnode = rpi_firmware_find_node(); + if (!fwnode) + return -ENXIO; + + pdev = of_find_device_by_node(fwnode); + dev = &pdev->dev; + + rc = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (rc) + return rc; + + fw = rpi_firmware_get(fwnode); + if (!fw) + return -ENXIO; + rc = rpi_firmware_property(fw, RPI_FIRMWARE_GET_VC_MEMORY, + &gpu_mem, sizeof(gpu_mem)); + if (rc) + return rc; + + gpu_mem.base = INTALIAS_NORMAL(gpu_mem.base); + + if (!gpu_mem.base || !gpu_mem.length) { + dev_err(dev, "%s: unable to determine gpu memory (%x,%x)\n", + __func__, gpu_mem.base, gpu_mem.length); + return -EFAULT; + } + + vcdma->cb_base = dma_alloc_wc(dev, SZ_4K, &vcdma->cb_handle, GFP_KERNEL); + if (!vcdma->cb_base) { + dev_err(dev, "failed to allocate DMA CBs\n"); + return -ENOMEM; + } + + rc = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK, + &vcdma->dma_chan_base, + &vcdma->dma_irq); + if (rc < 0) { + dev_err(dev, "failed to allocate a DMA channel\n"); + goto free_cb; + } + + vcdma->dma_chan = rc; + + init_waitqueue_head(&vcdma->dma_waitq); + + vcdma->dev = dev; + + return 0; + +free_cb: + dma_free_wc(dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle); + + return rc; +} + +static void +vc_mem_dma_uninit(void) +{ + struct vc_mem_dma *vcdma = &vc_mem_dma; + + if (vcdma->dev) { + bcm_dma_chan_free(vcdma->dma_chan); + dma_free_wc(vcdma->dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle); + vcdma->dev = NULL; + } +} + +static int dma_memcpy(struct vc_mem_dma *vcdma, dma_addr_t dst, dma_addr_t src, + int size) +{ + struct bcm2708_dma_cb *cb = vcdma->cb_base; + int burst_size = (vcdma->dma_chan == 0) ? 8 : 2; + + cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH | + BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | + BCM2708_DMA_D_INC; + cb->dst = dst; + cb->src = src; + cb->length = size; + cb->stride = 0; + cb->pad[0] = 0; + cb->pad[1] = 0; + cb->next = 0; + + bcm_dma_start(vcdma->dma_chan_base, vcdma->cb_handle); + bcm_dma_wait_idle(vcdma->dma_chan_base); + + return 0; +} + +static long vc_mem_copy(struct vc_mem_dmacopy *ioparam) +{ + struct vc_mem_dma *vcdma = &vc_mem_dma; + size_t size = PAGE_SIZE; + const u32 dma_xfer_chunk = 256; + u32 *buf = NULL; + dma_addr_t bus_addr; + long rc = 0; + size_t offset; + + /* restrict this to root user */ + if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) + return -EFAULT; + + if (mutex_lock_interruptible(&dma_mutex)) + return -EINTR; + + rc = vc_mem_dma_init(); + if (rc) + goto out; + + vcdma = &vc_mem_dma; + + if (INTALIAS_NORMAL(ioparam->src) < gpu_mem.base || + INTALIAS_NORMAL(ioparam->src) >= gpu_mem.base + gpu_mem.length) { + pr_err("%s: invalid memory access %x (%x-%x)", __func__, + INTALIAS_NORMAL(ioparam->src), gpu_mem.base, + gpu_mem.base + gpu_mem.length); + rc = -EFAULT; + goto out; + } + + buf = dma_alloc_coherent(vcdma->dev, PAGE_ALIGN(size), &bus_addr, + GFP_ATOMIC); + if (!buf) { + rc = -ENOMEM; + goto out; + } + + for (offset = 0; offset < ioparam->length; offset += size) { + size_t remaining = ioparam->length - offset; + size_t s = min(size, remaining); + u8 *p = (u8 *)((uintptr_t)ioparam->src + offset); + u8 *q = (u8 *)ioparam->dst + offset; + + rc = dma_memcpy(vcdma, bus_addr, + INTALIAS_L1L2_NONALLOCATING((u32)(uintptr_t)p), + (s + dma_xfer_chunk - 1) & ~(dma_xfer_chunk - 1)); + if (rc) { + dev_err(vcdma->dev, "dma_memcpy failed\n"); + break; + } + if (copy_to_user(q, buf, s) != 0) { + pr_err("%s: copy_to_user failed\n", __func__); + rc = -EFAULT; + break; + } + } + +out: + if (buf) + dma_free_coherent(vcdma->dev, PAGE_ALIGN(size), buf, + bus_addr); + + mutex_unlock(&dma_mutex); + + return rc; +} + +static long +vc_mem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + (void) cmd; + (void) arg; + + pr_debug("%s: called file = 0x%p, cmd %08x\n", __func__, file, cmd); + + switch (cmd) { + case VC_MEM_IOC_MEM_PHYS_ADDR: + { + pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR=0x%p\n", + __func__, (void *)mm_vc_mem_phys_addr); + + if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr, + sizeof(mm_vc_mem_phys_addr))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_MEM_SIZE: + { + /* Get the videocore memory size first */ + vc_mem_get_size(); + + pr_debug("%s: VC_MEM_IOC_MEM_SIZE=%x\n", __func__, + mm_vc_mem_size); + + if (copy_to_user((void *)arg, &mm_vc_mem_size, + sizeof(mm_vc_mem_size))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_MEM_BASE: + { + /* Get the videocore memory base */ + vc_mem_get_base(); + + pr_debug("%s: VC_MEM_IOC_MEM_BASE=%x\n", __func__, + mm_vc_mem_base); + + if (copy_to_user((void *)arg, &mm_vc_mem_base, + sizeof(mm_vc_mem_base))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_MEM_LOAD: + { + /* Get the videocore memory base */ + vc_mem_get_base(); + + pr_debug("%s: VC_MEM_IOC_MEM_LOAD=%x\n", __func__, + mm_vc_mem_base); + + if (copy_to_user((void *)arg, &mm_vc_mem_base, + sizeof(mm_vc_mem_base))) { + rc = -EFAULT; + } + break; + } + case VC_MEM_IOC_DMACOPY: + { + struct vc_mem_dmacopy ioparam; + /* Get the parameter data. + */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam))) { + pr_err("%s: copy_from_user failed\n", __func__); + rc = -EFAULT; + break; + } + + rc = vc_mem_copy(&ioparam); + break; + } + default: + { + return -ENOTTY; + } + } + pr_debug("%s: file = 0x%p returning %d\n", __func__, file, rc); + + return rc; +} + +#ifdef CONFIG_COMPAT +static long +vc_mem_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + + switch (cmd) { + case VC_MEM_IOC_MEM_PHYS_ADDR32: + pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR32=0x%p\n", + __func__, (void *)mm_vc_mem_phys_addr); + + /* This isn't correct, but will cover us for now as + * VideoCore is 32bit only. + */ + if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr, + sizeof(compat_ulong_t))) + rc = -EFAULT; + + break; + + case VC_MEM_IOC_DMACOPY32: + { + struct vc_mem_dmacopy32 param32; + struct vc_mem_dmacopy param; + /* Get the parameter data. + */ + if (copy_from_user(¶m32, (void *)arg, sizeof(param32))) { + pr_err("%s: copy_from_user failed\n", __func__); + rc = -EFAULT; + break; + } + param.dst = compat_ptr(param32.dst); + param.src = param32.src; + param.length = param32.length; + rc = vc_mem_copy(¶m); + break; + } + + default: + rc = vc_mem_ioctl(file, cmd, arg); + break; + } + + return rc; +} +#endif + +static int +vc_mem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int rc = 0; + unsigned long length = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + pr_debug("%s: vm_start = 0x%08lx vm_end = 0x%08lx vm_pgoff = 0x%08lx\n", + __func__, (long)vma->vm_start, (long)vma->vm_end, + (long)vma->vm_pgoff); + + if (offset + length > mm_vc_mem_size) { + pr_err("%s: length %ld is too big\n", __func__, length); + return -EINVAL; + } + /* Do not cache the memory map */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + rc = remap_pfn_range(vma, vma->vm_start, + (mm_vc_mem_phys_addr >> PAGE_SHIFT) + + vma->vm_pgoff, length, vma->vm_page_prot); + if (rc) + pr_err("%s: remap_pfn_range failed (rc=%d)\n", __func__, rc); + + return rc; +} + +/* File Operations for the driver. */ +static const struct file_operations vc_mem_fops = { + .owner = THIS_MODULE, + .open = vc_mem_open, + .release = vc_mem_release, + .unlocked_ioctl = vc_mem_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vc_mem_compat_ioctl, +#endif + .mmap = vc_mem_mmap, +}; + +#ifdef CONFIG_DEBUG_FS +static void vc_mem_debugfs_deinit(void) +{ + debugfs_remove_recursive(vc_mem_debugfs_entry); + vc_mem_debugfs_entry = NULL; +} + + +static int vc_mem_debugfs_init( + struct device *dev) +{ + vc_mem_debugfs_entry = debugfs_create_dir(DRIVER_NAME, NULL); + if (!vc_mem_debugfs_entry) { + dev_warn(dev, "could not create debugfs entry\n"); + return -EFAULT; + } + + debugfs_create_x32("vc_mem_phys_addr", + 0444, + vc_mem_debugfs_entry, + (u32 *)&mm_vc_mem_phys_addr); + debugfs_create_x32("vc_mem_size", + 0444, + vc_mem_debugfs_entry, + (u32 *)&mm_vc_mem_size); + debugfs_create_x32("vc_mem_base", + 0444, + vc_mem_debugfs_entry, + (u32 *)&mm_vc_mem_base); + + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +/* Module load/unload functions */ + +static int __init +vc_mem_init(void) +{ + int rc = -EFAULT; + struct device *dev; + + pr_debug("%s: called\n", __func__); + + mm_vc_mem_phys_addr = phys_addr; + mm_vc_mem_size = mem_size; + mm_vc_mem_base = mem_base; + + vc_mem_get_size(); + + pr_info("vc-mem: phys_addr:0x%08lx mem_base=0x%08x mem_size:0x%08x(%u MiB)\n", + mm_vc_mem_phys_addr, mm_vc_mem_base, mm_vc_mem_size, + mm_vc_mem_size / (1024 * 1024)); + + rc = alloc_chrdev_region(&vc_mem_devnum, 0, 1, DRIVER_NAME); + if (rc < 0) { + pr_err("%s: alloc_chrdev_region failed (rc=%d)\n", + __func__, rc); + goto out_err; + } + + cdev_init(&vc_mem_cdev, &vc_mem_fops); + rc = cdev_add(&vc_mem_cdev, vc_mem_devnum, 1); + if (rc) { + pr_err("%s: cdev_add failed (rc=%d)\n", __func__, rc); + goto out_unregister; + } + + vc_mem_class = class_create(DRIVER_NAME); + if (IS_ERR(vc_mem_class)) { + rc = PTR_ERR(vc_mem_class); + pr_err("%s: class_create failed (rc=%d)\n", __func__, rc); + goto out_cdev_del; + } + + dev = device_create(vc_mem_class, NULL, vc_mem_devnum, NULL, + DRIVER_NAME); + if (IS_ERR(dev)) { + rc = PTR_ERR(dev); + pr_err("%s: device_create failed (rc=%d)\n", __func__, rc); + goto out_class_destroy; + } + +#ifdef CONFIG_DEBUG_FS + /* don't fail if the debug entries cannot be created */ + vc_mem_debugfs_init(dev); +#endif + + mutex_init(&dma_mutex); + vc_mem_inited = 1; + return 0; + +out_class_destroy: + class_destroy(vc_mem_class); + vc_mem_class = NULL; + +out_cdev_del: + cdev_del(&vc_mem_cdev); + +out_unregister: + unregister_chrdev_region(vc_mem_devnum, 1); + +out_err: + return -1; +} + +static void __exit +vc_mem_exit(void) +{ + pr_debug("%s: called\n", __func__); + + vc_mem_dma_uninit(); + if (vc_mem_inited) { +#ifdef CONFIG_DEBUG_FS + vc_mem_debugfs_deinit(); +#endif + device_destroy(vc_mem_class, vc_mem_devnum); + class_destroy(vc_mem_class); + cdev_del(&vc_mem_cdev); + unregister_chrdev_region(vc_mem_devnum, 1); + vc_mem_inited = 0; + } +} + +module_init(vc_mem_init); +module_exit(vc_mem_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Broadcom Corporation"); + +module_param(phys_addr, uint, 0644); +module_param(mem_size, uint, 0644); +module_param(mem_base, uint, 0644); diff --git a/drivers/char/broadcom/vcio.c b/drivers/char/broadcom/vcio.c new file mode 100644 index 000000000000..9db2408c781a --- /dev/null +++ b/drivers/char/broadcom/vcio.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2010 Broadcom + * Copyright (C) 2015 Noralf Trønnes + * Copyright (C) 2021 Raspberry Pi (Trading) Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/compat.h> +#include <linux/miscdevice.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define MODULE_NAME "vcio" +#define VCIO_IOC_MAGIC 100 +#define IOCTL_MBOX_PROPERTY _IOWR(VCIO_IOC_MAGIC, 0, char *) +#ifdef CONFIG_COMPAT +#define IOCTL_MBOX_PROPERTY32 _IOWR(VCIO_IOC_MAGIC, 0, compat_uptr_t) +#endif + +struct vcio_data { + struct rpi_firmware *fw; + struct miscdevice misc_dev; +}; + +static int vcio_user_property_list(struct vcio_data *vcio, void *user) +{ + u32 *buf, size; + int ret; + + /* The first 32-bit is the size of the buffer */ + if (copy_from_user(&size, user, sizeof(size))) + return -EFAULT; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, user, size)) { + kfree(buf); + return -EFAULT; + } + + /* Strip off protocol encapsulation */ + ret = rpi_firmware_property_list(vcio->fw, &buf[2], size - 12); + if (ret) { + kfree(buf); + return ret; + } + + buf[1] = RPI_FIRMWARE_STATUS_SUCCESS; + if (copy_to_user(user, buf, size)) + ret = -EFAULT; + + kfree(buf); + + return ret; +} + +static int vcio_device_open(struct inode *inode, struct file *file) +{ + try_module_get(THIS_MODULE); + + return 0; +} + +static int vcio_device_release(struct inode *inode, struct file *file) +{ + module_put(THIS_MODULE); + + return 0; +} + +static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct vcio_data *vcio = container_of(file->private_data, + struct vcio_data, misc_dev); + + switch (ioctl_num) { + case IOCTL_MBOX_PROPERTY: + return vcio_user_property_list(vcio, (void *)ioctl_param); + default: + pr_err("unknown ioctl: %x\n", ioctl_num); + return -EINVAL; + } +} + +#ifdef CONFIG_COMPAT +static long vcio_device_compat_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct vcio_data *vcio = container_of(file->private_data, + struct vcio_data, misc_dev); + + switch (ioctl_num) { + case IOCTL_MBOX_PROPERTY32: + return vcio_user_property_list(vcio, compat_ptr(ioctl_param)); + default: + pr_err("unknown ioctl: %x\n", ioctl_num); + return -EINVAL; + } +} +#endif + +const struct file_operations vcio_fops = { + .unlocked_ioctl = vcio_device_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vcio_device_compat_ioctl, +#endif + .open = vcio_device_open, + .release = vcio_device_release, +}; + +static int vcio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *fw_node; + struct rpi_firmware *fw; + struct vcio_data *vcio; + + fw_node = of_get_parent(np); + if (!fw_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = rpi_firmware_get(fw_node); + of_node_put(fw_node); + if (!fw) + return -EPROBE_DEFER; + + vcio = devm_kzalloc(dev, sizeof(struct vcio_data), GFP_KERNEL); + if (!vcio) + return -ENOMEM; + + vcio->fw = fw; + vcio->misc_dev.fops = &vcio_fops; + vcio->misc_dev.minor = MISC_DYNAMIC_MINOR; + vcio->misc_dev.name = "vcio"; + vcio->misc_dev.parent = dev; + + return misc_register(&vcio->misc_dev); +} + +static void vcio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + misc_deregister(dev_get_drvdata(dev)); +} + +static const struct of_device_id vcio_ids[] = { + { .compatible = "raspberrypi,vcio" }, + { } +}; +MODULE_DEVICE_TABLE(of, vcio_ids); + +static struct platform_driver vcio_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(vcio_ids), + }, + .probe = vcio_probe, + .remove = vcio_remove, +}; + +module_platform_driver(vcio_driver); + +MODULE_AUTHOR("Gray Girling"); +MODULE_AUTHOR("Noralf Trønnes"); +MODULE_DESCRIPTION("Mailbox userspace access"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rpi-vcio"); |
