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
bfin-otp.c
/*
 * Blackfin On-Chip OTP Memory Interface
 *
 * Copyright 2007-2009 Analog Devices Inc.
 *
 * Enter bugs at http://blackfin.uclinux.org/
 *
 * Licensed under the GPL-2 or later.
 */

#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <mtd/mtd-abi.h>

#include <asm/blackfin.h>
#include <asm/bfrom.h>
#include <asm/uaccess.h>

#define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args)
#define stampit() stamp("here i am")
#define pr_init(fmt, args...) ({ static const __initconst char __fmt[] = fmt; printk(__fmt, ## args); })

#define DRIVER_NAME "bfin-otp"
#define PFX DRIVER_NAME ": "

static DEFINE_MUTEX(bfin_otp_lock);

/**
 *	bfin_otp_read - Read OTP pages
 *
 *	All reads must be in half page chunks (half page == 64 bits).
 */
static ssize_t bfin_otp_read(struct file *file, char __user *buff, size_t count, loff_t *pos)
{
	ssize_t bytes_done;
	u32 page, flags, ret;
	u64 content;

	stampit();

	if (count % sizeof(u64))
		return -EMSGSIZE;

	if (mutex_lock_interruptible(&bfin_otp_lock))
		return -ERESTARTSYS;

	bytes_done = 0;
	page = *pos / (sizeof(u64) * 2);
	while (bytes_done < count) {
		flags = (*pos % (sizeof(u64) * 2) ? OTP_UPPER_HALF : OTP_LOWER_HALF);
		stamp("processing page %i (0x%x:%s)", page, flags,
			(flags & OTP_UPPER_HALF ? "upper" : "lower"));
		ret = bfrom_OtpRead(page, flags, &content);
		if (ret & OTP_MASTER_ERROR) {
			stamp("error from otp: 0x%x", ret);
			bytes_done = -EIO;
			break;
		}
		if (copy_to_user(buff + bytes_done, &content, sizeof(content))) {
			bytes_done = -EFAULT;
			break;
		}
		if (flags & OTP_UPPER_HALF)
			++page;
		bytes_done += sizeof(content);
		*pos += sizeof(content);
	}

	mutex_unlock(&bfin_otp_lock);

	return bytes_done;
}

#ifdef CONFIG_BFIN_OTP_WRITE_ENABLE
static bool allow_writes;

/**
 *	bfin_otp_init_timing - setup OTP timing parameters
 *
 *	Required before doing any write operation.  Algorithms from HRM.
 */
static u32 bfin_otp_init_timing(void)
{
	u32 tp1, tp2, tp3, timing;

	tp1 = get_sclk() / 1000000;
	tp2 = (2 * get_sclk() / 10000000) << 8;
	tp3 = (0x1401) << 15;
	timing = tp1 | tp2 | tp3;
	if (bfrom_OtpCommand(OTP_INIT, timing))
		return 0;

	return timing;
}

/**
 *	bfin_otp_deinit_timing - set timings to only allow reads
 *
 *	Should be called after all writes are done.
 */
static void bfin_otp_deinit_timing(u32 timing)
{
	/* mask bits [31:15] so that any attempts to write fail */
	bfrom_OtpCommand(OTP_CLOSE, 0);
	bfrom_OtpCommand(OTP_INIT, timing & ~(-1 << 15));
	bfrom_OtpCommand(OTP_CLOSE, 0);
}

/**
 *	bfin_otp_write - write OTP pages
 *
 *	All writes must be in half page chunks (half page == 64 bits).
 */
static ssize_t bfin_otp_write(struct file *filp, const char __user *buff, size_t count, loff_t *pos)
{
	ssize_t bytes_done;
	u32 timing, page, base_flags, flags, ret;
	u64 content;

	if (!allow_writes)
		return -EACCES;

	if (count % sizeof(u64))
		return -EMSGSIZE;

	if (mutex_lock_interruptible(&bfin_otp_lock))
		return -ERESTARTSYS;

	stampit();

	timing = bfin_otp_init_timing();
	if (timing == 0) {
		mutex_unlock(&bfin_otp_lock);
		return -EIO;
	}

	base_flags = OTP_CHECK_FOR_PREV_WRITE;

	bytes_done = 0;
	page = *pos / (sizeof(u64) * 2);
	while (bytes_done < count) {
		flags = base_flags | (*pos % (sizeof(u64) * 2) ? OTP_UPPER_HALF : OTP_LOWER_HALF);
		stamp("processing page %i (0x%x:%s) from %p", page, flags,
			(flags & OTP_UPPER_HALF ? "upper" : "lower"), buff + bytes_done);
		if (copy_from_user(&content, buff + bytes_done, sizeof(content))) {
			bytes_done = -EFAULT;
			break;
		}
		ret = bfrom_OtpWrite(page, flags, &content);
		if (ret & OTP_MASTER_ERROR) {
			stamp("error from otp: 0x%x", ret);
			bytes_done = -EIO;
			break;
		}
		if (flags & OTP_UPPER_HALF)
			++page;
		bytes_done += sizeof(content);
		*pos += sizeof(content);
	}

	bfin_otp_deinit_timing(timing);

	mutex_unlock(&bfin_otp_lock);

	return bytes_done;
}

static long bfin_otp_ioctl(struct file *filp, unsigned cmd, unsigned long arg)
{
	stampit();

	switch (cmd) {
	case OTPLOCK: {
		u32 timing;
		int ret = -EIO;

		if (!allow_writes)
			return -EACCES;

		if (mutex_lock_interruptible(&bfin_otp_lock))
			return -ERESTARTSYS;

		timing = bfin_otp_init_timing();
		if (timing) {
			u32 otp_result = bfrom_OtpWrite(arg, OTP_LOCK, NULL);
			stamp("locking page %lu resulted in 0x%x", arg, otp_result);
			if (!(otp_result & OTP_MASTER_ERROR))
				ret = 0;

			bfin_otp_deinit_timing(timing);
		}

		mutex_unlock(&bfin_otp_lock);

		return ret;
	}

	case MEMLOCK:
		allow_writes = false;
		return 0;

	case MEMUNLOCK:
		allow_writes = true;
		return 0;
	}

	return -EINVAL;
}
#else
# define bfin_otp_write NULL
# define bfin_otp_ioctl NULL
#endif

static const struct file_operations bfin_otp_fops = {
	.owner          = THIS_MODULE,
	.unlocked_ioctl = bfin_otp_ioctl,
	.read           = bfin_otp_read,
	.write          = bfin_otp_write,
	.llseek		= default_llseek,
};

static struct miscdevice bfin_otp_misc_device = {
	.minor    = MISC_DYNAMIC_MINOR,
	.name     = DRIVER_NAME,
	.fops     = &bfin_otp_fops,
};
module_misc_device(bfin_otp_misc_device);

MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
MODULE_DESCRIPTION("Blackfin OTP Memory Interface");
MODULE_LICENSE("GPL");
back to top