Revision dc280d93623927570da279e99393879dbbab39e7 authored by Thomas Gleixner on 21 December 2016, 19:19:49 UTC, committed by Thomas Gleixner on 25 December 2016, 09:47:42 UTC
Developers manage to overwrite states blindly without thought. That's fatal
and hard to debug. Add sanity checks to make it fail.

This requries to restructure the code so that the dynamic state allocation
happens in the same lock protected section as the actual store. Otherwise
the previous assignment of 'Reserved' to the name field would trigger the
overwrite check.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Sebastian Siewior <bigeasy@linutronix.de>
Link: http://lkml.kernel.org/r/20161221192111.675234535@linutronix.de
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>

1 parent 59fefd0
Raw File
vexpress-syscfg.c
/*
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Copyright (C) 2014 ARM Limited
 */

#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/syscore_ops.h>
#include <linux/vexpress.h>


#define SYS_CFGDATA		0x0

#define SYS_CFGCTRL		0x4
#define SYS_CFGCTRL_START	(1 << 31)
#define SYS_CFGCTRL_WRITE	(1 << 30)
#define SYS_CFGCTRL_DCC(n)	(((n) & 0xf) << 26)
#define SYS_CFGCTRL_FUNC(n)	(((n) & 0x3f) << 20)
#define SYS_CFGCTRL_SITE(n)	(((n) & 0x3) << 16)
#define SYS_CFGCTRL_POSITION(n)	(((n) & 0xf) << 12)
#define SYS_CFGCTRL_DEVICE(n)	(((n) & 0xfff) << 0)

#define SYS_CFGSTAT		0x8
#define SYS_CFGSTAT_ERR		(1 << 1)
#define SYS_CFGSTAT_COMPLETE	(1 << 0)


struct vexpress_syscfg {
	struct device *dev;
	void __iomem *base;
	struct list_head funcs;
};

struct vexpress_syscfg_func {
	struct list_head list;
	struct vexpress_syscfg *syscfg;
	struct regmap *regmap;
	int num_templates;
	u32 template[0]; /* Keep it last! */
};


static int vexpress_syscfg_exec(struct vexpress_syscfg_func *func,
		int index, bool write, u32 *data)
{
	struct vexpress_syscfg *syscfg = func->syscfg;
	u32 command, status;
	int tries;
	long timeout;

	if (WARN_ON(index > func->num_templates))
		return -EINVAL;

	command = readl(syscfg->base + SYS_CFGCTRL);
	if (WARN_ON(command & SYS_CFGCTRL_START))
		return -EBUSY;

	command = func->template[index];
	command |= SYS_CFGCTRL_START;
	command |= write ? SYS_CFGCTRL_WRITE : 0;

	/* Use a canary for reads */
	if (!write)
		*data = 0xdeadbeef;

	dev_dbg(syscfg->dev, "func %p, command %x, data %x\n",
			func, command, *data);
	writel(*data, syscfg->base + SYS_CFGDATA);
	writel(0, syscfg->base + SYS_CFGSTAT);
	writel(command, syscfg->base + SYS_CFGCTRL);
	mb();

	/* The operation can take ages... Go to sleep, 100us initially */
	tries = 100;
	timeout = 100;
	do {
		if (!irqs_disabled()) {
			set_current_state(TASK_INTERRUPTIBLE);
			schedule_timeout(usecs_to_jiffies(timeout));
			if (signal_pending(current))
				return -EINTR;
		} else {
			udelay(timeout);
		}

		status = readl(syscfg->base + SYS_CFGSTAT);
		if (status & SYS_CFGSTAT_ERR)
			return -EFAULT;

		if (timeout > 20)
			timeout -= 20;
	} while (--tries && !(status & SYS_CFGSTAT_COMPLETE));
	if (WARN_ON_ONCE(!tries))
		return -ETIMEDOUT;

	if (!write) {
		*data = readl(syscfg->base + SYS_CFGDATA);
		dev_dbg(syscfg->dev, "func %p, read data %x\n", func, *data);
	}

	return 0;
}

static int vexpress_syscfg_read(void *context, unsigned int index,
		unsigned int *val)
{
	struct vexpress_syscfg_func *func = context;

	return vexpress_syscfg_exec(func, index, false, val);
}

static int vexpress_syscfg_write(void *context, unsigned int index,
		unsigned int val)
{
	struct vexpress_syscfg_func *func = context;

	return vexpress_syscfg_exec(func, index, true, &val);
}

static struct regmap_config vexpress_syscfg_regmap_config = {
	.lock = vexpress_config_lock,
	.unlock = vexpress_config_unlock,
	.reg_bits = 32,
	.val_bits = 32,
	.reg_read = vexpress_syscfg_read,
	.reg_write = vexpress_syscfg_write,
	.reg_format_endian = REGMAP_ENDIAN_LITTLE,
	.val_format_endian = REGMAP_ENDIAN_LITTLE,
};


static struct regmap *vexpress_syscfg_regmap_init(struct device *dev,
		void *context)
{
	int err;
	struct vexpress_syscfg *syscfg = context;
	struct vexpress_syscfg_func *func;
	struct property *prop;
	const __be32 *val = NULL;
	__be32 energy_quirk[4];
	int num;
	u32 site, position, dcc;
	int i;

	err = vexpress_config_get_topo(dev->of_node, &site,
				&position, &dcc);
	if (err)
		return ERR_PTR(err);

	prop = of_find_property(dev->of_node,
			"arm,vexpress-sysreg,func", NULL);
	if (!prop)
		return ERR_PTR(-EINVAL);

	num = prop->length / sizeof(u32) / 2;
	val = prop->value;

	/*
	 * "arm,vexpress-energy" function used to be described
	 * by its first device only, now it requires both
	 */
	if (num == 1 && of_device_is_compatible(dev->of_node,
			"arm,vexpress-energy")) {
		num = 2;
		energy_quirk[0] = *val;
		energy_quirk[2] = *val++;
		energy_quirk[1] = *val;
		energy_quirk[3] = cpu_to_be32(be32_to_cpup(val) + 1);
		val = energy_quirk;
	}

	func = kzalloc(sizeof(*func) + sizeof(*func->template) * num,
			GFP_KERNEL);
	if (!func)
		return ERR_PTR(-ENOMEM);

	func->syscfg = syscfg;
	func->num_templates = num;

	for (i = 0; i < num; i++) {
		u32 function, device;

		function = be32_to_cpup(val++);
		device = be32_to_cpup(val++);

		dev_dbg(dev, "func %p: %u/%u/%u/%u/%u\n",
				func, site, position, dcc,
				function, device);

		func->template[i] = SYS_CFGCTRL_DCC(dcc);
		func->template[i] |= SYS_CFGCTRL_SITE(site);
		func->template[i] |= SYS_CFGCTRL_POSITION(position);
		func->template[i] |= SYS_CFGCTRL_FUNC(function);
		func->template[i] |= SYS_CFGCTRL_DEVICE(device);
	}

	vexpress_syscfg_regmap_config.max_register = num - 1;

	func->regmap = regmap_init(dev, NULL, func,
			&vexpress_syscfg_regmap_config);

	if (IS_ERR(func->regmap)) {
		void *err = func->regmap;

		kfree(func);
		return err;
	}

	list_add(&func->list, &syscfg->funcs);

	return func->regmap;
}

static void vexpress_syscfg_regmap_exit(struct regmap *regmap, void *context)
{
	struct vexpress_syscfg *syscfg = context;
	struct vexpress_syscfg_func *func, *tmp;

	regmap_exit(regmap);

	list_for_each_entry_safe(func, tmp, &syscfg->funcs, list) {
		if (func->regmap == regmap) {
			list_del(&syscfg->funcs);
			kfree(func);
			break;
		}
	}
}

static struct vexpress_config_bridge_ops vexpress_syscfg_bridge_ops = {
	.regmap_init = vexpress_syscfg_regmap_init,
	.regmap_exit = vexpress_syscfg_regmap_exit,
};


static int vexpress_syscfg_probe(struct platform_device *pdev)
{
	struct vexpress_syscfg *syscfg;
	struct resource *res;
	struct device *bridge;

	syscfg = devm_kzalloc(&pdev->dev, sizeof(*syscfg), GFP_KERNEL);
	if (!syscfg)
		return -ENOMEM;
	syscfg->dev = &pdev->dev;
	INIT_LIST_HEAD(&syscfg->funcs);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!devm_request_mem_region(&pdev->dev, res->start,
			resource_size(res), pdev->name))
		return -EBUSY;

	syscfg->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
	if (!syscfg->base)
		return -EFAULT;

	/* Must use dev.parent (MFD), as that's where DT phandle points at... */
	bridge = vexpress_config_bridge_register(pdev->dev.parent,
			&vexpress_syscfg_bridge_ops, syscfg);
	if (IS_ERR(bridge))
		return PTR_ERR(bridge);

	return 0;
}

static const struct platform_device_id vexpress_syscfg_id_table[] = {
	{ "vexpress-syscfg", },
	{},
};

static struct platform_driver vexpress_syscfg_driver = {
	.driver.name = "vexpress-syscfg",
	.id_table = vexpress_syscfg_id_table,
	.probe = vexpress_syscfg_probe,
};

static int __init vexpress_syscfg_init(void)
{
	return platform_driver_register(&vexpress_syscfg_driver);
}
core_initcall(vexpress_syscfg_init);
back to top