aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char')
-rw-r--r--drivers/char/Kconfig10
-rw-r--r--drivers/char/Makefile2
-rw-r--r--drivers/char/broadcom/Kconfig33
-rw-r--r--drivers/char/broadcom/Makefile3
-rw-r--r--drivers/char/broadcom/bcm2835_smi_dev.c408
-rw-r--r--drivers/char/broadcom/vc_mem.c635
-rw-r--r--drivers/char/broadcom/vcio.c187
-rw-r--r--drivers/char/hw_random/Kconfig2
-rw-r--r--drivers/char/hw_random/bcm2835-rng.c26
-rw-r--r--drivers/char/hw_random/iproc-rng200.c79
-rw-r--r--drivers/char/random.c8
-rw-r--r--drivers/char/raspberrypi-gpiomem.c275
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(&param32, (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(&param);
+ 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]>");