Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a37cf19 authored by Alexey Korolev's avatar Alexey Korolev Committed by David Woodhouse
Browse files

[MTD] [NOR] Fix deadlock in Intel chip driver caused by get_chip recursion



This patch solves kernel deadlock issue seen on JFFF2 simultaneous 
operations. Detailed investigation of the issue showed that the kernel 
deadlock is caused by tons of recursive get_chip calls.

Signed-off-by: default avatarAlexey Korolev <akorolev@infradead.org>
Acked-by: default avatarNicolas Pitre <nico@cam.org>
Signed-off-by: default avatarDavid Woodhouse <dwmw2@infradead.org>
parent 2a754b51
Loading
Loading
Loading
Loading
+77 −69
Original line number Original line Diff line number Diff line
@@ -85,6 +85,7 @@ static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len,
static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from,
static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from,
			size_t len);
			size_t len);


static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
#include "fwh_lock.h"
#include "fwh_lock.h"
@@ -641,73 +642,13 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd,
/*
/*
 *  *********** CHIP ACCESS FUNCTIONS ***********
 *  *********** CHIP ACCESS FUNCTIONS ***********
 */
 */

static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
{
{
	DECLARE_WAITQUEUE(wait, current);
	DECLARE_WAITQUEUE(wait, current);
	struct cfi_private *cfi = map->fldrv_priv;
	struct cfi_private *cfi = map->fldrv_priv;
	map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01);
	map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01);
	unsigned long timeo;
	struct cfi_pri_intelext *cfip = cfi->cmdset_priv;
	struct cfi_pri_intelext *cfip = cfi->cmdset_priv;

	unsigned long timeo = jiffies + HZ;
 resettime:
	timeo = jiffies + HZ;
 retry:
	if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING || mode == FL_OTP_WRITE || mode == FL_SHUTDOWN)) {
		/*
		 * OK. We have possibility for contension on the write/erase
		 * operations which are global to the real chip and not per
		 * partition.  So let's fight it over in the partition which
		 * currently has authority on the operation.
		 *
		 * The rules are as follows:
		 *
		 * - any write operation must own shared->writing.
		 *
		 * - any erase operation must own _both_ shared->writing and
		 *   shared->erasing.
		 *
		 * - contension arbitration is handled in the owner's context.
		 *
		 * The 'shared' struct can be read and/or written only when
		 * its lock is taken.
		 */
		struct flchip_shared *shared = chip->priv;
		struct flchip *contender;
		spin_lock(&shared->lock);
		contender = shared->writing;
		if (contender && contender != chip) {
			/*
			 * The engine to perform desired operation on this
			 * partition is already in use by someone else.
			 * Let's fight over it in the context of the chip
			 * currently using it.  If it is possible to suspend,
			 * that other partition will do just that, otherwise
			 * it'll happily send us to sleep.  In any case, when
			 * get_chip returns success we're clear to go ahead.
			 */
			int ret = spin_trylock(contender->mutex);
			spin_unlock(&shared->lock);
			if (!ret)
				goto retry;
			spin_unlock(chip->mutex);
			ret = get_chip(map, contender, contender->start, mode);
			spin_lock(chip->mutex);
			if (ret) {
				spin_unlock(contender->mutex);
				return ret;
			}
			timeo = jiffies + HZ;
			spin_lock(&shared->lock);
			spin_unlock(contender->mutex);
		}

		/* We now own it */
		shared->writing = chip;
		if (mode == FL_ERASING)
			shared->erasing = chip;
		spin_unlock(&shared->lock);
	}


	switch (chip->state) {
	switch (chip->state) {


@@ -722,16 +663,11 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
			if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS))
			if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS))
				break;
				break;


			if (time_after(jiffies, timeo)) {
				printk(KERN_ERR "%s: Waiting for chip to be ready timed out. Status %lx\n",
				       map->name, status.x[0]);
				return -EIO;
			}
			spin_unlock(chip->mutex);
			spin_unlock(chip->mutex);
			cfi_udelay(1);
			cfi_udelay(1);
			spin_lock(chip->mutex);
			spin_lock(chip->mutex);
			/* Someone else might have been playing with it. */
			/* Someone else might have been playing with it. */
			goto retry;
			return -EAGAIN;
		}
		}


	case FL_READY:
	case FL_READY:
@@ -809,8 +745,80 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
		schedule();
		schedule();
		remove_wait_queue(&chip->wq, &wait);
		remove_wait_queue(&chip->wq, &wait);
		spin_lock(chip->mutex);
		spin_lock(chip->mutex);
		goto resettime;
		return -EAGAIN;
	}
}

static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
{
	int ret;

 retry:
	if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING
			   || mode == FL_OTP_WRITE || mode == FL_SHUTDOWN)) {
		/*
		 * OK. We have possibility for contention on the write/erase
		 * operations which are global to the real chip and not per
		 * partition.  So let's fight it over in the partition which
		 * currently has authority on the operation.
		 *
		 * The rules are as follows:
		 *
		 * - any write operation must own shared->writing.
		 *
		 * - any erase operation must own _both_ shared->writing and
		 *   shared->erasing.
		 *
		 * - contention arbitration is handled in the owner's context.
		 *
		 * The 'shared' struct can be read and/or written only when
		 * its lock is taken.
		 */
		struct flchip_shared *shared = chip->priv;
		struct flchip *contender;
		spin_lock(&shared->lock);
		contender = shared->writing;
		if (contender && contender != chip) {
			/*
			 * The engine to perform desired operation on this
			 * partition is already in use by someone else.
			 * Let's fight over it in the context of the chip
			 * currently using it.  If it is possible to suspend,
			 * that other partition will do just that, otherwise
			 * it'll happily send us to sleep.  In any case, when
			 * get_chip returns success we're clear to go ahead.
			 */
			ret = spin_trylock(contender->mutex);
			spin_unlock(&shared->lock);
			if (!ret)
				goto retry;
			spin_unlock(chip->mutex);
			ret = chip_ready(map, contender, contender->start, mode);
			spin_lock(chip->mutex);

			if (ret == -EAGAIN) {
				spin_unlock(contender->mutex);
				goto retry;
			}
			if (ret) {
				spin_unlock(contender->mutex);
				return ret;
			}
			spin_lock(&shared->lock);
			spin_unlock(contender->mutex);
		}
		}

		/* We now own it */
		shared->writing = chip;
		if (mode == FL_ERASING)
			shared->erasing = chip;
		spin_unlock(&shared->lock);
	}
	ret = chip_ready(map, chip, adr, mode);
	if (ret == -EAGAIN)
		goto retry;

	return ret;
}
}


static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr)
static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr)