diff options
Diffstat (limited to 'drivers/char')
| -rw-r--r-- | drivers/char/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/char/Makefile | 2 | ||||
| -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 | ||||
| -rw-r--r-- | drivers/char/hw_random/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/char/hw_random/bcm2835-rng.c | 26 | ||||
| -rw-r--r-- | drivers/char/hw_random/iproc-rng200.c | 79 | ||||
| -rw-r--r-- | drivers/char/random.c | 8 | ||||
| -rw-r--r-- | drivers/char/raspberrypi-gpiomem.c | 275 |
12 files changed, 1656 insertions, 12 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index d2cfc584e202..ff87476d945a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -5,6 +5,8 @@ menu "Character devices" +source "drivers/char/broadcom/Kconfig" + source "drivers/tty/Kconfig" config TTY_PRINTK @@ -423,4 +425,12 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. +config RASPBERRYPI_GPIOMEM + tristate "Rootless GPIO access via mmap() on Raspberry Pi boards" + default n + help + Provides users with root-free access to the GPIO registers + on the board. Calling mmap(/dev/gpiomem) will map the GPIO + register page to the user's pointer. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 1291369b9126..2c5ea59dfec6 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -44,3 +44,5 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_XILLYBUS_CLASS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o +obj-$(CONFIG_BRCM_CHAR_DRIVERS) += broadcom/ +obj-$(CONFIG_RASPBERRYPI_GPIOMEM) += raspberrypi-gpiomem.o 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"); diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 492a2a61a65b..dbc993fb9e01 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -131,7 +131,7 @@ config HW_RANDOM_IPROC_RNG200 default HW_RANDOM help This driver provides kernel-side support for the RNG200 - hardware found on the Broadcom iProc and STB SoCs. + hardware found on the Broadcom iProc, BCM2711 and STB SoCs. To compile this driver as a module, choose M here: the module will be called iproc-rng200 diff --git a/drivers/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c index aa2b135e3ee2..05a4569ce28f 100644 --- a/drivers/char/hw_random/bcm2835-rng.c +++ b/drivers/char/hw_random/bcm2835-rng.c @@ -13,6 +13,7 @@ #include <linux/printk.h> #include <linux/clk.h> #include <linux/reset.h> +#include <linux/delay.h> #define RNG_CTRL 0x0 #define RNG_STATUS 0x4 @@ -27,6 +28,9 @@ #define RNG_INT_OFF 0x1 +#define RNG_FIFO_WORDS 4 +#define RNG_US_PER_WORD 34 /* Tuned for throughput */ + struct bcm2835_rng_priv { struct hwrng rng; void __iomem *base; @@ -63,19 +67,23 @@ static inline void rng_writel(struct bcm2835_rng_priv *priv, u32 val, static int bcm2835_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) { + u32 retries = 1000000/(RNG_FIFO_WORDS * RNG_US_PER_WORD); struct bcm2835_rng_priv *priv = to_rng_priv(rng); u32 max_words = max / sizeof(u32); u32 num_words, count; - while ((rng_readl(priv, RNG_STATUS) >> 24) == 0) { - if (!wait) + num_words = rng_readl(priv, RNG_STATUS) >> 24; + + while (!num_words) { + if (!wait || !retries) return 0; - hwrng_yield(rng); + retries--; + usleep_range((u32)RNG_US_PER_WORD, + (u32)RNG_US_PER_WORD * RNG_FIFO_WORDS); + num_words = rng_readl(priv, RNG_STATUS) >> 24; } - num_words = rng_readl(priv, RNG_STATUS) >> 24; - if (num_words > max_words) - num_words = max_words; + num_words = min(num_words, max_words); for (count = 0; count < num_words; count++) ((u32 *)buf)[count] = rng_readl(priv, RNG_DATA); @@ -107,8 +115,10 @@ static int bcm2835_rng_init(struct hwrng *rng) } /* set warm-up count & enable */ - rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS); - rng_writel(priv, RNG_RBGEN, RNG_CTRL); + if (!(rng_readl(priv, RNG_CTRL) & RNG_RBGEN)) { + rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS); + rng_writel(priv, RNG_RBGEN, RNG_CTRL); + } return ret; } diff --git a/drivers/char/hw_random/iproc-rng200.c b/drivers/char/hw_random/iproc-rng200.c index 440fe28bddc0..33bc28f429f6 100644 --- a/drivers/char/hw_random/iproc-rng200.c +++ b/drivers/char/hw_random/iproc-rng200.c @@ -13,6 +13,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mod_devicetable.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/delay.h> @@ -20,6 +21,7 @@ #define RNG_CTRL_OFFSET 0x00 #define RNG_CTRL_RNG_RBGEN_MASK 0x00001FFF #define RNG_CTRL_RNG_RBGEN_ENABLE 0x00000001 +#define RNG_CTRL_RNG_DIV_CTRL_SHIFT 13 #define RNG_SOFT_RESET_OFFSET 0x04 #define RNG_SOFT_RESET 0x00000001 @@ -27,16 +29,23 @@ #define RBG_SOFT_RESET_OFFSET 0x08 #define RBG_SOFT_RESET 0x00000001 +#define RNG_TOTAL_BIT_COUNT_OFFSET 0x0C + +#define RNG_TOTAL_BIT_COUNT_THRESHOLD_OFFSET 0x10 + #define RNG_INT_STATUS_OFFSET 0x18 #define RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK 0x80000000 #define RNG_INT_STATUS_STARTUP_TRANSITIONS_MET_IRQ_MASK 0x00020000 #define RNG_INT_STATUS_NIST_FAIL_IRQ_MASK 0x00000020 #define RNG_INT_STATUS_TOTAL_BITS_COUNT_IRQ_MASK 0x00000001 +#define RNG_INT_ENABLE_OFFSET 0x1C + #define RNG_FIFO_DATA_OFFSET 0x20 #define RNG_FIFO_COUNT_OFFSET 0x24 #define RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK 0x000000FF +#define RNG_FIFO_COUNT_RNG_FIFO_THRESHOLD_SHIFT 8 struct iproc_rng200_dev { struct hwrng rng; @@ -157,6 +166,64 @@ static int iproc_rng200_init(struct hwrng *rng) return 0; } +static int bcm2711_rng200_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + u32 max_words = max / sizeof(u32); + u32 num_words, count, val; + + /* ensure warm up period has elapsed */ + while (1) { + val = ioread32(priv->base + RNG_TOTAL_BIT_COUNT_OFFSET); + if (val > 16) + break; + cpu_relax(); + } + + /* ensure fifo is not empty */ + while (1) { + num_words = ioread32(priv->base + RNG_FIFO_COUNT_OFFSET) & + RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK; + if (num_words) + break; + if (!wait) + return 0; + cpu_relax(); + } + + if (num_words > max_words) + num_words = max_words; + + for (count = 0; count < num_words; count++) { + ((u32 *)buf)[count] = ioread32(priv->base + + RNG_FIFO_DATA_OFFSET); + } + + return num_words * sizeof(u32); +} + +static int bcm2711_rng200_init(struct hwrng *rng) +{ + struct iproc_rng200_dev *priv = to_rng_priv(rng); + uint32_t val; + + if (ioread32(priv->base + RNG_CTRL_OFFSET) & RNG_CTRL_RNG_RBGEN_MASK) + return 0; + + /* initial numbers generated are "less random" so will be discarded */ + val = 0x40000; + iowrite32(val, priv->base + RNG_TOTAL_BIT_COUNT_THRESHOLD_OFFSET); + /* min fifo count to generate full interrupt */ + val = 2 << RNG_FIFO_COUNT_RNG_FIFO_THRESHOLD_SHIFT; + iowrite32(val, priv->base + RNG_FIFO_COUNT_OFFSET); + /* enable the rng - 1Mhz sample rate */ + val = (0x3 << RNG_CTRL_RNG_DIV_CTRL_SHIFT) | RNG_CTRL_RNG_RBGEN_MASK; + iowrite32(val, priv->base + RNG_CTRL_OFFSET); + + return 0; +} + static void iproc_rng200_cleanup(struct hwrng *rng) { struct iproc_rng200_dev *priv = to_rng_priv(rng); @@ -183,11 +250,17 @@ static int iproc_rng200_probe(struct platform_device *pdev) dev_set_drvdata(dev, priv); - priv->rng.name = "iproc-rng200"; - priv->rng.read = iproc_rng200_read; - priv->rng.init = iproc_rng200_init; + priv->rng.name = pdev->name; priv->rng.cleanup = iproc_rng200_cleanup; + if (of_device_is_compatible(dev->of_node, "brcm,bcm2711-rng200")) { + priv->rng.init = bcm2711_rng200_init; + priv->rng.read = bcm2711_rng200_read; + } else { + priv->rng.init = iproc_rng200_init; + priv->rng.read = iproc_rng200_read; + } + /* Register driver */ ret = devm_hwrng_register(dev, &priv->rng); if (ret) { diff --git a/drivers/char/random.c b/drivers/char/random.c index b8b24b6ed3fe..14240e93ad20 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -869,6 +869,14 @@ void __init random_init_early(const char *command_line) unsigned long entropy[BLAKE2S_BLOCK_SIZE / sizeof(long)]; size_t i, longs, arch_bits; + /* + * If we were initialized by the bootloader before jump labels are + * initialized, then we should enable the static branch here, where + * it's guaranteed that jump labels have been initialized. + */ + if (!static_branch_likely(&crng_is_ready) && crng_init >= CRNG_READY) + crng_set_ready(NULL); + #if defined(LATENT_ENTROPY_PLUGIN) static const u8 compiletime_seed[BLAKE2S_BLOCK_SIZE] __initconst __latent_entropy; _mix_pool_bytes(compiletime_seed, sizeof(compiletime_seed)); diff --git a/drivers/char/raspberrypi-gpiomem.c b/drivers/char/raspberrypi-gpiomem.c new file mode 100644 index 000000000000..0f9b15bc14a8 --- /dev/null +++ b/drivers/char/raspberrypi-gpiomem.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/** + * raspberrypi-gpiomem.c + * + * Provides MMIO access to discontiguous section of Device memory as a linear + * user mapping. Successor to bcm2835-gpiomem.c. + * + * Copyright (c) 2023, Raspberry Pi Ltd. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/cdev.h> +#include <linux/pagemap.h> +#include <linux/io.h> + +#define DRIVER_NAME "rpi-gpiomem" +#define DEVICE_MINOR 0 + +/* + * Sensible max for a hypothetical "gpio" controller that splits pads, + * IO controls, GPIO in/out/enable, and function selection into different + * ranges. Most use only one or two. + */ +#define MAX_RANGES 4 + +struct io_windows { + unsigned long phys_base; + unsigned long len; +}; + +struct rpi_gpiomem_priv { + dev_t devid; + struct class *class; + struct cdev rpi_gpiomem_cdev; + struct device *dev; + const char *name; + unsigned int nr_wins; + struct io_windows iowins[4]; +}; + +static int rpi_gpiomem_open(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + struct rpi_gpiomem_priv *priv; + + if (dev != DEVICE_MINOR) + ret = -ENXIO; + + priv = container_of(inode->i_cdev, struct rpi_gpiomem_priv, + rpi_gpiomem_cdev); + if (!priv) + return -EINVAL; + file->private_data = priv; + return ret; +} + +static int rpi_gpiomem_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + + if (dev != DEVICE_MINOR) + ret = -ENXIO; + + return ret; +} + +static const struct vm_operations_struct rpi_gpiomem_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys +#endif +}; + +static int rpi_gpiomem_mmap(struct file *file, struct vm_area_struct *vma) +{ + int i; + struct rpi_gpiomem_priv *priv; + unsigned long base; + unsigned long len = 0; + unsigned long offset; + + priv = file->private_data; + /* + * Userspace must provide a virtual address space at least + * the size of the concatenated ranges. + */ + for (i = 0; i < priv->nr_wins; i++) + len += priv->iowins[i].len; + if (len > vma->vm_end - vma->vm_start + 1) + return -EINVAL; + + vma->vm_ops = &rpi_gpiomem_vm_ops; + offset = vma->vm_start; + for (i = 0; i < priv->nr_wins; i++) { + base = priv->iowins[i].phys_base >> PAGE_SHIFT; + len = priv->iowins[i].len; + vma->vm_page_prot = phys_mem_access_prot(file, base, len, + vma->vm_page_prot); + if (remap_pfn_range(vma, offset, + base, len, + vma->vm_page_prot)) + break; + offset += len; + } + + if (i < priv->nr_wins) + return -EAGAIN; + + return 0; +} + +static const struct file_operations rpi_gpiomem_fops = { + .owner = THIS_MODULE, + .open = rpi_gpiomem_open, + .release = rpi_gpiomem_release, + .mmap = rpi_gpiomem_mmap, +}; + +static const struct of_device_id rpi_gpiomem_of_match[]; + +static int rpi_gpiomem_probe(struct platform_device *pdev) +{ + int err, i; + const struct of_device_id *id; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct resource *ioresource; + struct rpi_gpiomem_priv *priv; + + /* Allocate buffers and instance data */ + + priv = kzalloc(sizeof(struct rpi_gpiomem_priv), GFP_KERNEL); + + if (!priv) { + err = -ENOMEM; + goto failed_inst_alloc; + } + platform_set_drvdata(pdev, priv); + + priv->dev = dev; + id = of_match_device(rpi_gpiomem_of_match, dev); + if (!id) + return -EINVAL; + + /* + * Device node naming - for legacy (bcm2835) DT bindings, the driver + * created the node based on a hardcoded name - for new bindings, + * take the node name from DT. + */ + if (id == &rpi_gpiomem_of_match[0]) { + priv->name = "gpiomem"; + } else { + err = of_property_read_string(node, "chardev-name", &priv->name); + if (err) + return -EINVAL; + } + + /* + * Go find the register ranges associated with this instance + */ + for (i = 0; i < MAX_RANGES; i++) { + ioresource = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!ioresource && i == 0) { + dev_err(priv->dev, "failed to get IO resource - no ranges available\n"); + err = -ENOENT; + goto failed_get_resource; + } + if (!ioresource) + break; + + priv->iowins[i].phys_base = ioresource->start; + priv->iowins[i].len = (ioresource->end + 1) - ioresource->start; + dev_info(&pdev->dev, "window base 0x%08lx size 0x%08lx\n", + priv->iowins[i].phys_base, priv->iowins[i].len); + priv->nr_wins++; + } + + /* Create character device entries */ + + err = alloc_chrdev_region(&priv->devid, + DEVICE_MINOR, 1, priv->name); + if (err != 0) { + dev_err(priv->dev, "unable to allocate device number"); + goto failed_alloc_chrdev; + } + cdev_init(&priv->rpi_gpiomem_cdev, &rpi_gpiomem_fops); + priv->rpi_gpiomem_cdev.owner = THIS_MODULE; + err = cdev_add(&priv->rpi_gpiomem_cdev, priv->devid, 1); + if (err != 0) { + dev_err(priv->dev, "unable to register device"); + goto failed_cdev_add; + } + + /* Create sysfs entries */ + + priv->class = class_create(priv->name); + if (IS_ERR(priv->class)) { + err = PTR_ERR(priv->class); + goto failed_class_create; + } + + dev = device_create(priv->class, NULL, priv->devid, NULL, priv->name); + if (IS_ERR(dev)) { + err = PTR_ERR(dev); + goto failed_device_create; + } + + dev_info(priv->dev, "initialised %u regions as /dev/%s\n", + priv->nr_wins, priv->name); + + return 0; + +failed_device_create: + class_destroy(priv->class); +failed_class_create: + cdev_del(&priv->rpi_gpiomem_cdev); +failed_cdev_add: + unregister_chrdev_region(priv->devid, 1); +failed_alloc_chrdev: +failed_get_resource: + kfree(priv); +failed_inst_alloc: + dev_err(&pdev->dev, "could not load rpi_gpiomem"); + return err; +} + +static void rpi_gpiomem_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpi_gpiomem_priv *priv = platform_get_drvdata(pdev); + + device_destroy(priv->class, priv->devid); + class_destroy(priv->class); + cdev_del(&priv->rpi_gpiomem_cdev); + unregister_chrdev_region(priv->devid, 1); + kfree(priv); + + dev_info(dev, "%s driver removed - OK", priv->name); +} + +static const struct of_device_id rpi_gpiomem_of_match[] = { + { + .compatible = "brcm,bcm2835-gpiomem", + }, + { + .compatible = "raspberrypi,gpiomem", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rpi_gpiomem_of_match); + +static struct platform_driver rpi_gpiomem_driver = { + .probe = rpi_gpiomem_probe, + .remove = rpi_gpiomem_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = rpi_gpiomem_of_match, + }, +}; + +module_platform_driver(rpi_gpiomem_driver); + +MODULE_ALIAS("platform:rpi-gpiomem"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Driver for accessing GPIOs from userspace"); +MODULE_AUTHOR("Jonathan Bell <[email protected]>"); |
