Subject: [PATCH] misc: atmel-secumod: Driver for Atmel "security module".

From: [email-masked] (Alexandre Belloni)
Date: Mon, 25 Jan 2016 12:09:21 +0100

Hi,

On 20/01/2016 at 20:57:35 -0700, David Mosberger-Tang wrote :
> From: David Mosberger <[email-masked]>
> 
> SAMA5D2 (and perhaps other SOCs) implements a secure module which
> hosts a battery-backed SRAM with is sectioned into three different
> areas with different properties: a 4KiB auto-erasable secure RAM, a
> 1KiB non-auto-eraseable secure RAM, and 32 bytes of (non-secure) RAM.
> 
> This driver provides (minimal) access to these RAM areas through
> sysfs.  For example, adding this to the DTS:
> 
> 	secumod at fc040000 {
> 		compatible = "atmel,sama5d2-secumod";
> 		reg = <0xfc040000 0x4000>;
> 		status = "okay";
> 
> 		#address-cells = <1>;
> 		#size-cells = <1>;
> 
> 		secram_auto_erasable at f8044000 {
> 			reg = <0xf8044000 0x1000>;
> 		};
> 		secram at f8045000 {
> 			reg = <0xf8045000 0x400>;
> 		};
> 		ram at f8045400 {
> 			reg = <0xf8045400 0x20>;
> 		};
> 	};
> 
> would provide binary files "ram", "secram", and "secram_auto_erasable"
> in directory /sys/bus/platform/devices/fc040000.secumod.
> This files can then be read or written with any user-level tool.
> 
> The driver is minimal in the sense that it doesn't provide (yet)
> a way to manage scrambling keys etc.
> 

I know this does more than that but I think those thre sections should
be registered using the nvmem framework. The sysfs file creation and
accesses then comes for free.

Another idea is also to expose it using a genpool so it can be accessed
as sram from inside the kernel.

> Signed-off-by: David Mosberger <[email-masked]>
> ---
>  arch/arm/boot/dts/sama5d2.dtsi |  19 ++++
>  drivers/misc/Kconfig           |   7 ++
>  drivers/misc/Makefile          |   1 +
>  drivers/misc/atmel-secumod.c   | 224 +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 251 insertions(+)
>  create mode 100644 drivers/misc/atmel-secumod.c
> 
> diff --git a/arch/arm/boot/dts/sama5d2.dtsi b/arch/arm/boot/dts/sama5d2.dtsi
> index 89d4de8..d4bd3b6 100644
> --- a/arch/arm/boot/dts/sama5d2.dtsi
> +++ b/arch/arm/boot/dts/sama5d2.dtsi
> @@ -1239,6 +1239,25 @@
>  				clocks = <&pioA_clk>;
>  			};
>  
> +			secumod at fc040000 {
> +				compatible = "atmel,sama5d2-secumod";
> +				reg = <0xfc040000 0x4000>;
> +				status = "okay";
> +
> +				#address-cells = <1>;
> +				#size-cells = <1>;
> +
> +				secram_auto_erasable at f8044000 {
> +					reg = <0xf8044000 0x1000>;
> +				};
> +				secram at f8045000 {
> +					reg = <0xf8045000 0x400>;
> +				};
> +				ram at f8045400 {
> +					reg = <0xf8045400 0x20>;
> +				};
> +			};
> +
>  			tdes at fc044000 {
>  				compatible = "atmel,at91sam9g46-tdes";
>  				reg = <0xfc044000 0x100>;
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 006242c..74a8ee5 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -84,6 +84,13 @@ config ATMEL_TCB_CLKSRC_BLOCK
>  	  TC can be used for other purposes, such as PWM generation and
>  	  interval timing.
>  
> +config ATMEL_SECUMOD
> +       tristate "Atmel Secure Module driver"
> +       depends on ARCH_AT91
> +       help
> +         Select this to get support for the secure module (SECUMOD) built
> +	 into the SAMA5D2 chips.
> +
>  config DUMMY_IRQ
>  	tristate "Dummy IRQ handler"
>  	default n
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index 7d5c4cd..e1f661d 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_AD525X_DPOT_SPI)	+= ad525x_dpot-spi.o
>  obj-$(CONFIG_INTEL_MID_PTI)	+= pti.o
>  obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
>  obj-$(CONFIG_ATMEL_TCLIB)	+= atmel_tclib.o
> +obj-$(CONFIG_ATMEL_SECUMOD)	+= atmel-secumod.o
>  obj-$(CONFIG_BMP085)		+= bmp085.o
>  obj-$(CONFIG_BMP085_I2C)	+= bmp085-i2c.o
>  obj-$(CONFIG_BMP085_SPI)	+= bmp085-spi.o
> diff --git a/drivers/misc/atmel-secumod.c b/drivers/misc/atmel-secumod.c
> new file mode 100644
> index 0000000..5ac1a18
> --- /dev/null
> +++ b/drivers/misc/atmel-secumod.c
> @@ -0,0 +1,224 @@
> +/*
> + * Driver for SAMA5D2 secure module (SECUMOD).
> + *
> + * Copyright (C) 2016 eGauge Systems LLC
> + *
> + * David Mosberger <[email-masked]>
> + *
> + * 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/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +
> +#include <asm/io.h>
> +
> +/*
> + * Security-module register definitions:
> + */
> +#define SECUMOD_RAMRDY	0x0014
> +
> +struct ram_area {
> +	struct list_head next;
> +	void *regs;	/* ioremapped RAM area */
> +	size_t size;	/* size in bytes */
> +	struct bin_attribute attr;
> +};
> +
> +struct secumod_device {
> +	struct spinlock lock;
> +	void __iomem *regs;		/* ioremapped SECUMOD registers */
> +	struct platform_device *pdev;
> +	struct list_head ram_areas;
> +};
> +
> +static ssize_t
> +secumod_ram_write(struct file *filp, struct kobject *kobj,
> +		  struct bin_attribute *attr,
> +		  char *buf, loff_t off, size_t count)
> +{
> +	struct ram_area *ram = attr->private;
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct secumod_device *secumod = platform_get_drvdata(pdev);
> +
> +	if (off < 0 || off >= ram->size)
> +		return -EINVAL;
> +	if (off + count > ram->size)
> +		count = ram->size = off;
> +
> +	if (count > 0) {
> +		spin_lock(&secumod->lock);
> +		memcpy_toio(ram->regs + off, buf, count);
> +		spin_unlock(&secumod->lock);
> +	}
> +	return count;
> +}
> +
> +static ssize_t
> +secumod_ram_read(struct file *filp, struct kobject *kobj,
> +		 struct bin_attribute *attr,
> +		 char *buf, loff_t off, size_t count)
> +{
> +	struct ram_area *ram = attr->private;
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct secumod_device *secumod = platform_get_drvdata(pdev);
> +
> +	if (off < 0 || off >= ram->size)
> +		return -EINVAL;
> +	if (off + count > ram->size)
> +		count = ram->size = off;
> +
> +	if (count > 0) {
> +		spin_lock(&secumod->lock);
> +		memcpy_fromio(buf, ram->regs + off, count);
> +		spin_unlock(&secumod->lock);
> +	}
> +	return count;
> +}
> +
> +static void
> +secumod_register_ram(struct secumod_device *secumod,
> +		     const char *name, unsigned long addr, size_t size)
> +{
> +	struct device *dev = &secumod->pdev->dev;
> +	struct ram_area *ram;
> +	int ret;
> +
> +	ram = devm_kzalloc(dev, sizeof(*ram), GFP_KERNEL);
> +	if (!ram) {
> +		dev_err(dev, "Out of memory for RAM area %s\n", name);
> +		return;
> +	}
> +	INIT_LIST_HEAD(&ram->next);
> +	ram->size = size;
> +	ram->regs = ioremap_nocache(addr, size);
> +	ram->attr.attr.name = name;
> +	ram->attr.attr.mode = S_IRUGO | S_IWUSR;
> +	ram->attr.private = ram;
> +	ram->attr.size = size;
> +	ram->attr.read = secumod_ram_read;
> +	ram->attr.write = secumod_ram_write;
> +	ret = device_create_bin_file(dev, &ram->attr);
> +	if (ret) {
> +		dev_err(dev, "Failed to register RAM area %s (%d)", name, ret);
> +		return;
> +	}
> +	spin_lock(&secumod->lock);
> +	list_add_tail(&ram->next, &secumod->ram_areas);
> +	spin_unlock(&secumod->lock);
> +	pr_info("atmel-secumod: RAM 0x%08lx-0x%08lx (%s)\n",
> +		addr, addr + size - 1, name);
> +}
> +
> +/*
> + * Since the secure module may need to automatically erase some of the
> + * RAM, it may take a while for it to be ready.  As far as I know,
> + * it's not documented how long this might take in the worst-case.
> + */
> +static void
> +secumod_wait_ready (struct secumod_device *secumod)
> +{
> +	unsigned long start, stop;
> +
> +	start = jiffies;
> +	while (!(readl(secumod->regs + SECUMOD_RAMRDY) & 1))
> +		msleep_interruptible(1);
> +	stop = jiffies;
> +	if (stop != start)
> +		pr_info("atmel-secumod: it took %u msec for SECUMOD "
> +			"to become ready...\n", jiffies_to_msecs(stop - start));
> +}
> +
> +static int
> +secumod_probe(struct platform_device *pdev)
> +{
> +	struct secumod_device *secumod;
> +	struct device_node *child;
> +	struct resource *regs;
> +	u32 addr, size;
> +	const void *pv;
> +	int len;
> +
> +	secumod = devm_kzalloc(&pdev->dev, sizeof(*secumod), GFP_KERNEL);
> +	if (!secumod) {
> +		dev_err(&pdev->dev, "out of memory\n");
> +		return -ENOMEM;
> +	}
> +	spin_lock_init(&secumod->lock);
> +	INIT_LIST_HEAD(&secumod->ram_areas);
> +
> +	if (!pdev->dev.of_node) {
> +		dev_err(&pdev->dev, "Missing of_node attribute\n");
> +		return -ENODEV;
> +	}
> +
> +	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!regs) {
> +		dev_err(&pdev->dev, "Missing secumod resources.\n");
> +		return -ENODEV;
> +	}
> +	secumod->pdev = pdev;
> +	secumod->regs = devm_ioremap_resource(&pdev->dev, regs);
> +
> +	platform_set_drvdata(pdev, secumod);
> +
> +	for_each_child_of_node(pdev->dev.of_node, child) {
> +		pv = of_get_property(child, "reg", &len);
> +		if (!pv ||
> +		    of_n_addr_cells(child) != 1 ||
> +		    of_n_size_cells(child) != 1) {
> +			dev_err(&pdev->dev, "Node %s missing \"reg\" property "
> +				"or incorrect address/size count.",
> +				child->name);
> +			return -ENODEV;
> +		}
> +		addr = be32_to_cpup(pv);
> +		size = be32_to_cpup(pv + 4);
> +		secumod_register_ram(secumod, child->name, addr, size);
> +	}
> +	secumod_wait_ready(secumod);
> +	return 0;
> +}
> +
> +static int
> +secumod_remove(struct platform_device *pdev)
> +{
> +	struct secumod_device *secumod = platform_get_drvdata(pdev);
> +	struct ram_area *ram;
> +
> +	spin_lock(&secumod->lock);
> +	list_for_each_entry(ram, &secumod->ram_areas, next)
> +		device_remove_bin_file(&pdev->dev, &ram->attr);
> +	spin_unlock(&secumod->lock);
> +	return 0;
> +}
> +
> +static const struct of_device_id secumod_dt_ids[] = {
> +	{
> +		.compatible = "atmel,sama5d2-secumod"
> +	},
> +	{
> +		/* sentinel */
> +	}
> +};
> +MODULE_DEVICE_TABLE(of, secumod_dt_ids);
> +
> +static struct platform_driver secumod_driver = {
> +	.driver = {
> +		.name = "atmel-secumod",
> +		.of_match_table = of_match_ptr(secumod_dt_ids),
> +	},
> +	.probe		= secumod_probe,
> +	.remove		= secumod_remove
> +};
> +module_platform_driver(secumod_driver);
> +
> +MODULE_AUTHOR("David Mosberger <[email-masked]>");
> +MODULE_DESCRIPTION("Atmel SAMA5D2 secure module driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.9.1
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

-- 
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com